useSelector 常量在分派后不更新

IT技术 reactjs redux react-redux
2021-05-07 21:36:57

这是一个Codesandbox

getIDs()更新cells,然后需要更新initializeCells()但是,此更改不会在分发操作后反映出来。尽管如此,我可以在Redux 开发工具上看到 action 已经通过,并且 的值cells也相应地发生了变化。gameStart()正在通过 props 传递给cells一个子组件,并通过useEffect()钩子在那里调用我需要传递一个空数组作为这个钩子的第二个参数,否则它将永远运行,因为每次调用它时状态都会更新。问题是新状态getIDs()在第一次运行后不适用于以下功能似乎是gameStart()完全完成并再次被调用的时候。我需要initializeCells()getIDs()完成后立即更新需要更新的状态

细胞.js

import React, { useEffect } from "react";
import { useSelector } from "react-redux";

import Cell from "./Container/Container/Cell";

const Cells = props => {
  const board = useSelector(state => state.board);

  useEffect(() => {
    props.gameStart();
  }, []);

  return (
    <div id="cells">
      {board.map(cell => {
        return (
          <Cell
            id={cell.id.substring(1)}
            key={cell.id.substring(1)}
            className="cell"
          />
        );
      })}
    </div>
  );
};

export default Cells;

应用程序.js

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";

import {
  setCells,
  setBoard
} from "../../redux/actions/index";

const Game = () => {
  const dispatch = useDispatch();

  const cells = useSelector(state => state.cells);
  const board = useSelector(state => state.board);
  const boardSize = useSelector(state => state.boardSize);

  async function gameStart() {
    await getIDs();
    console.log(cells); // []
    await initializeCells();
    await assignSnake();
    await placeFood();
    await paintCells();
  }

  function getIDs() {
    let cellID = "";
    let collection = [];

    for (let i = 1; i <= boardSize.rows; i++) {
      for (let j = 1; j <= boardSize.columns; j++) {
        cellID = `#cell-${i}-${j}`;

        collection.push(cellID);
      }
    }
    dispatch(setCells(collection));
    console.log(cells); // []
  }

  function initializeCells() {
    console.log(cells); // []
    const board = [];
    // for loop never runs because cells is empty
    for (let i = 0; i < cells.length; i++) {
      board.push(cell(cells[i]));
    }
    dispatch(setBoard(board));
    console.log("Board: ", board); // []
  }

  function cell(id) {
    return {
      id: id,
      row: id.match("-(.*)-")[1],
      column: id.substr(id.lastIndexOf("-") + 1),
      hasFood: false,
      hasSnake: false
    };
  }

  return (
  ...
  )
}

export default Game;

减速器/ index.js

import {
  SET_CELLS,
  SET_BOARD
} from "../constants/action-types";

const initialState = {
  board: [],
  cells: [],
  boardSize: {
    rows: 25,
    columns: 40
  }
};

const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case SET_CELLS:
      return Object.assign({}, state, {
        cells: action.payload
      });

    case SET_BOARD:
      return Object.assign({}, state, {
        board: action.payload
      });

    default:
      return state;
  }
};

动作/ index.js

import {
  SET_CELLS,
  SET_BOARD
} from "../constants/action-types";

export const setCells = payload => {
  return { type: SET_CELLS, payload };
};

export const setBoard = payload => {
  return { type: SET_BOARD, payload };
};

常量/动作类型.js

export const SET_CELLS = "SET_CELLS";
export const SET_BOARD = "SET_BOARD";
2个回答

分派某些动作后,将仅在下一次渲染时提供更新的存储。这对于带有钩子的函数和带有connectHOC 的类也是一样的

您需要更改代码,不要期望立即更改。我很难理解你在这里的意图,你可能会从渲染它来的东西开始,然后用动作来处理即发即弃的方法。它应该工作。

如果没有,请制作最少的样本(仅相关钩子 + 如何呈现数据)并描述您想要获得的内容(而不是“如何”)

我建议您重新考虑这里的所有模式,并在编写代码之前考虑哪些因素会影响您的决策。首先,为什么要这样设置状态?如果使用 state 是合理的,那么当您仅在您的组件中访问时,为什么要创建单独的cellsboardstate 值是否有任何值,例如boardCellsboardSize会被控制吗?也许当应用程序加载时它们会从远程网络资源中调用而您并不马上知道它们?如果对其中任何一个都没有,那么实际上没有任何充分的理由将它们存储在状态中,并且它们可以只是在组件外部初始化时声明的常量。如果是,代码应该适合用例。如果您打算让用户控制板的大小,您应该使用默认值初始化您的值,并在您的减速器中处理所有同步状态更改而没有副作用。

此外,正如您所知,您使用异步函数的方式是一种反模式。使用 Redux,如果您使用真正的异步函数,即调用网络资源,您可以使用thunks,并且getState()每次您需要在dispatch.

否则,您是否熟悉 的类组件生命周期模式componentDidUpdate本质上,您“监听”状态更改,并且仅在更改后调用依赖于更改状态的函数。您可以使用钩子做到这一点的一种方法是useEffect使用包含您所依赖的状态的依赖项数组,这意味着只有在这些依赖项发生更改时才会调用它,并且您可以在useEffect函数中进行进一步的条件检查(但永远不要换行useEffect)有条件的!)。但是,当使用对象或数组作为依赖项时,事情变得更加复杂,因为它使用严格的相等检查,因此您可能需要使用 ref 并比较 中的当前值和先前值useEffect,使用类似usePrevious hook 的东西

综上所述,在您当前的用例中,您不需要执行任何这些操作,因为您只是同步初始化静态值。如果boardSize值不受控制,我个人甚至不会将其存储在 state 中,但为了教育起见,您将如何在 reducer 中执行此操作。

首先,简单地disptach({ type: 'INITIALIZE_BOARD' })Game组件。

然后,将所有同步逻辑封装在减速器中:

const initialState = {
  board: [],
  boardSize: {
    rows: 25,
    columns: 40
  }
};

const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INITIALIZE_BOARD': {
      const { rows, columns } = state.boardSize
      const board = [];

      for (let row = 1; row <= rows; row++) {
        for (let column = 1; column <= columns; column++) {
          board.push({
            id: `#cell-${row}-${column}`,
            row,
            column,
            hasFood: false,
            hasSnake: false
          });
        }
      }

      return {
        ...state,
        board
      };
    }
    default:
      return state;
  }
};