useReducer 返回未定义而不是对象数组
useReducer returning undefined rather then array of objects
为了陷入反应钩子,我决定尝试制作蛇并使用 useReducer/useContext 状态管理。
现在我被阻止在我想要方向键改变活动图块状态的地方。在 useReducer 中,这似乎收到了正确的有效载荷,并创建了正确的有效载荷对象,但是当它更新状态时,它是未定义的?
按向下键会出现"TypeError: Cannot read property 'some' of undefined"错误,意思是蛇未定义。
Board.jsx
import React, { useContext, useEffect } from "react";
import Tile from "./Tile.jsx";
import { snakeContext } from '../contexts/snakeContext'
const Board = () => {
const {
state: {
snake, food, direction, gameOver
},
dispatch,
rows,
cols
} = useContext(snakeContext)
useEffect(() => {
const onKeyPress = (e) => {
switch (e.keyCode) {
case 38: //Up
return direction === "down" || dispatch({ type: 'DIRECTION', payload: "up" });
case 40: // Down
return direction === "up" || dispatch({ type: 'DIRECTION', payload: "down" });
case 37: //Left
return direction === "right" || dispatch({ type: 'DIRECTION', payload: "left" });
case 39: // Right
return direction === "left" ||
dispatch({ type: 'DIRECTION', payload: "right" });
default:
break;
}
};
window.addEventListener("keydown", onKeyPress);
return () => window.removeEventListener("keydown", onKeyPress);
}, [direction]);
useEffect(() => {
const interval = setInterval(() => {
switch (direction) {
case "up":
dispatch({ type: 'SNAKE', payload: { ...snake[0], y: snake[0].y - 1 } })
break
case "down":
dispatch({ type: 'SNAKE', payload: { ...snake[0], y: snake[0].y + 1 } })
break;
case "left":
dispatch({ type: 'SNAKE', payload: { ...snake[0], x: snake[0].x - 1 } })
break;
case "right":
dispatch({ type: 'SNAKE', payload: { ...snake[0], x: snake[0].x + 1 } })
break;
default:
break;
}
}, 500);
return () => clearInterval(interval);
});
const style = {
maxHeight: `${2 * rows}rem`,
maxWidth: `${2 * cols}rem`,
margin: "0 auto",
paddingTop: "4rem"
};
const isActiveMatchingState = (i, j) => {
return snake.some(snakeTile =>
snakeTile.y === i && snakeTile.x === j
)
}
const renderBoard = () => {
let grid = Array.from(Array(rows), () => new Array(cols));
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
grid[i][j] = (
<Tile
isActive={isActiveMatchingState(i, j)}
isFood={food.y === i && food.x === j}
key={`${[i, j]}`}
/>
);
}
}
return grid;
};
return (
gameOver ?
<div>GAME OVER</div> :
<div style={style}>{renderBoard()}</div>
)
};
export default Board;
snakeReducer.jsx
export const snakeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'SNAKE':
return [{ ...state.snake[0], x: payload.x, y: payload.y }]
case 'FOOD':
return { ...state, x: payload.x, y: payload.y };
case 'DIRECTION':
return { ...state, direction: payload };
case 'GAME_OVER':
return { ...state, gameOver: payload };
default:
throw new Error();
}
};
我的 useContext 设置使用建议的 useMemo - https://hswolff.com/blog/how-to-usecontext-with-usereducer/
snakeContext.js
import React, { createContext, useReducer, useMemo } from 'react';
import { snakeReducer } from '../reducers/snakeReducer';
export const snakeContext = createContext();
const rows = 20;
const cols = 15;
const randomPosition = (biggestNumber) => Math.floor(Math.random() * biggestNumber)
const initialState = {
snake: [{ x: 0, y: 0 }],
food: { x: randomPosition(rows), y: randomPosition(cols) },
direction: null,
gameOver: false
};
const SnakeContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(snakeReducer, initialState);
const contextValue = useMemo(() => ({ state, rows, cols, dispatch }), [state, dispatch]);
return <snakeContext.Provider value={contextValue}>{children}</snakeContext.Provider>;
};
export default SnakeContextProvider;
App.js
import React from 'react';
import Home from './pages/Home';
import SnakeContextProvider from './contexts/snakeContext';
import './App.css';
const App = () => {
return (
<SnakeContextProvider>
<Home />
</SnakeContextProvider>
)
};
export default App;
Home.jsx 是一个包含 Board.jsx
的页面组件
奇怪的是,方向键的更新更新正常,所以 useReducer 似乎设置正确。
完整的当前 repo 在这里 - https://github.com/puyanwei/snake
谢谢!
你能更新一下吗?
export const snakeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'SNAKE':
return [{ ...state.snake[0], x: payload.x, y: payload.y }]
case 'FOOD':
return { ...state, x: payload.x, y: payload.y };
case 'DIRECTION':
return { ...state, direction: payload };
case 'GAME_OVER':
return { ...state, gameOver: payload };
default:
return state; //error here: throw new Error();
}
};
reducer 中SNAKE
动作的处理好像不太对。您正在 return 创建一个数组,但您可能期待一个类似于初始状态的状态,对吗?
const initialState = {
snake: [{ x: 0, y: 0 }],
prev: { x: null, y: null },
food: { x: randomPosition(rows), y: randomPosition(cols) },
direction: null,
gameOver: false
};
SNAKE
操作的减速器的 return 值是这样的,因为 snake[0]
是 { x:..., y: ...}
:
[{ x: payload.x, y: payload.y }]
最终是我的reducer,正确的return for 'SNAKE'应该是;
case 'SNAKE':
return {
...state,
snake: [{ x: payload.x, y: payload.y }, ...state.snake]
};
感谢所有帮助过的人!
问题出在 Board.jsx 内部,您从 useContext 获取状态数据,您必须获取数组形式的值,示例:
const [
state: {snake, food, direction, gameOver},
dispatch,
rows,
cols
] = useContext(snakeContext)
为了陷入反应钩子,我决定尝试制作蛇并使用 useReducer/useContext 状态管理。
现在我被阻止在我想要方向键改变活动图块状态的地方。在 useReducer 中,这似乎收到了正确的有效载荷,并创建了正确的有效载荷对象,但是当它更新状态时,它是未定义的?
按向下键会出现"TypeError: Cannot read property 'some' of undefined"错误,意思是蛇未定义。
Board.jsx
import React, { useContext, useEffect } from "react";
import Tile from "./Tile.jsx";
import { snakeContext } from '../contexts/snakeContext'
const Board = () => {
const {
state: {
snake, food, direction, gameOver
},
dispatch,
rows,
cols
} = useContext(snakeContext)
useEffect(() => {
const onKeyPress = (e) => {
switch (e.keyCode) {
case 38: //Up
return direction === "down" || dispatch({ type: 'DIRECTION', payload: "up" });
case 40: // Down
return direction === "up" || dispatch({ type: 'DIRECTION', payload: "down" });
case 37: //Left
return direction === "right" || dispatch({ type: 'DIRECTION', payload: "left" });
case 39: // Right
return direction === "left" ||
dispatch({ type: 'DIRECTION', payload: "right" });
default:
break;
}
};
window.addEventListener("keydown", onKeyPress);
return () => window.removeEventListener("keydown", onKeyPress);
}, [direction]);
useEffect(() => {
const interval = setInterval(() => {
switch (direction) {
case "up":
dispatch({ type: 'SNAKE', payload: { ...snake[0], y: snake[0].y - 1 } })
break
case "down":
dispatch({ type: 'SNAKE', payload: { ...snake[0], y: snake[0].y + 1 } })
break;
case "left":
dispatch({ type: 'SNAKE', payload: { ...snake[0], x: snake[0].x - 1 } })
break;
case "right":
dispatch({ type: 'SNAKE', payload: { ...snake[0], x: snake[0].x + 1 } })
break;
default:
break;
}
}, 500);
return () => clearInterval(interval);
});
const style = {
maxHeight: `${2 * rows}rem`,
maxWidth: `${2 * cols}rem`,
margin: "0 auto",
paddingTop: "4rem"
};
const isActiveMatchingState = (i, j) => {
return snake.some(snakeTile =>
snakeTile.y === i && snakeTile.x === j
)
}
const renderBoard = () => {
let grid = Array.from(Array(rows), () => new Array(cols));
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
grid[i][j] = (
<Tile
isActive={isActiveMatchingState(i, j)}
isFood={food.y === i && food.x === j}
key={`${[i, j]}`}
/>
);
}
}
return grid;
};
return (
gameOver ?
<div>GAME OVER</div> :
<div style={style}>{renderBoard()}</div>
)
};
export default Board;
snakeReducer.jsx
export const snakeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'SNAKE':
return [{ ...state.snake[0], x: payload.x, y: payload.y }]
case 'FOOD':
return { ...state, x: payload.x, y: payload.y };
case 'DIRECTION':
return { ...state, direction: payload };
case 'GAME_OVER':
return { ...state, gameOver: payload };
default:
throw new Error();
}
};
我的 useContext 设置使用建议的 useMemo - https://hswolff.com/blog/how-to-usecontext-with-usereducer/
snakeContext.js
import React, { createContext, useReducer, useMemo } from 'react';
import { snakeReducer } from '../reducers/snakeReducer';
export const snakeContext = createContext();
const rows = 20;
const cols = 15;
const randomPosition = (biggestNumber) => Math.floor(Math.random() * biggestNumber)
const initialState = {
snake: [{ x: 0, y: 0 }],
food: { x: randomPosition(rows), y: randomPosition(cols) },
direction: null,
gameOver: false
};
const SnakeContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(snakeReducer, initialState);
const contextValue = useMemo(() => ({ state, rows, cols, dispatch }), [state, dispatch]);
return <snakeContext.Provider value={contextValue}>{children}</snakeContext.Provider>;
};
export default SnakeContextProvider;
App.js
import React from 'react';
import Home from './pages/Home';
import SnakeContextProvider from './contexts/snakeContext';
import './App.css';
const App = () => {
return (
<SnakeContextProvider>
<Home />
</SnakeContextProvider>
)
};
export default App;
Home.jsx 是一个包含 Board.jsx
的页面组件奇怪的是,方向键的更新更新正常,所以 useReducer 似乎设置正确。
完整的当前 repo 在这里 - https://github.com/puyanwei/snake
谢谢!
你能更新一下吗?
export const snakeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'SNAKE':
return [{ ...state.snake[0], x: payload.x, y: payload.y }]
case 'FOOD':
return { ...state, x: payload.x, y: payload.y };
case 'DIRECTION':
return { ...state, direction: payload };
case 'GAME_OVER':
return { ...state, gameOver: payload };
default:
return state; //error here: throw new Error();
}
};
reducer 中SNAKE
动作的处理好像不太对。您正在 return 创建一个数组,但您可能期待一个类似于初始状态的状态,对吗?
const initialState = {
snake: [{ x: 0, y: 0 }],
prev: { x: null, y: null },
food: { x: randomPosition(rows), y: randomPosition(cols) },
direction: null,
gameOver: false
};
SNAKE
操作的减速器的 return 值是这样的,因为 snake[0]
是 { x:..., y: ...}
:
[{ x: payload.x, y: payload.y }]
最终是我的reducer,正确的return for 'SNAKE'应该是;
case 'SNAKE':
return {
...state,
snake: [{ x: payload.x, y: payload.y }, ...state.snake]
};
感谢所有帮助过的人!
问题出在 Board.jsx 内部,您从 useContext 获取状态数据,您必须获取数组形式的值,示例:
const [
state: {snake, food, direction, gameOver},
dispatch,
rows,
cols
] = useContext(snakeContext)