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)