【问题标题】:useSelector constant not updating after dispatchuseSelector 常量在调度后不更新
【发布时间】:2019-12-30 03:11:30
【问题描述】:

这是Codesandbox

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

cells.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;

app.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;

reducers/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;
  }
};

actions/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 };
};

constants/action-types.js

export const SET_CELLS = "SET_CELLS";
export const SET_BOARD = "SET_BOARD";

【问题讨论】:

  • 看来你需要在组件逻辑中使用useEffect,使用依赖数组。
  • 你能扩展一下吗?我不明白你的意思。
  • 当然。现在您正在组件内部定义函数,但是当它们内部的参数发生变化时,需要再次调用这些函数。为了实现这一点,我认为应该使用useEffect
  • 我已经更改了调用函数的组件。它最初是通过componentDidMount() 调用的,现在它使用useEffect()。在此之后,函数被无限调用,所以我必须将一个空数组作为第二个参数传递给useEffect()。然而,这并没有改变任何东西,我仍然遇到同样的问题。
  • 能否展开组件,gameStart 在哪里被调用?它在渲染什么?

标签: reactjs redux react-redux


【解决方案1】:

调度一些动作后,更新的存储将仅在下一次渲染时提供。这对于带有 connect HOC 的钩子和类的函数是相同的。

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

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

【讨论】:

    【解决方案2】:

    我建议您重新考虑这里的所有模式,并在编写代码之前考虑是什么影响了您的决策。首先,为什么要这样设置状态?如果使用状态是合理的,为什么在您仅在 Cells 组件中访问 board 时创建单独的 cellsboard 状态值? boardSize 之类的值是否会受到控制?当应用程序加载并且您不立即知道它们时,它们可能会从远程网络资源中调用?如果对其中任何一个都不是,则没有任何充分的理由将它们存储在状态中,它们可以只是在组件外部初始化时声明的常量。如果是,代码应该适合用例。如果你打算让用户控制板子大小,你应该用默认值初始化你的值,并在你的 reducer 中处理所有同步状态变化而没有副作用。

    另外,您知道,您使用异步函数的方式是一种反模式。使用 Redux,如果您使用真正的异步函数,即调用网络资源,则可以使用 thunks,并在每次需要访问 dispatch 之后的更新状态时在 thunk 中调用 getState()

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

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

    首先,只需来自Game 组件的disptach({ type: 'INITIALIZE_BOARD' })

    然后,将所有同步逻辑封装在 reducer 中:

    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;
      }
    };
    

    【讨论】:

      猜你喜欢
      • 2021-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      • 2022-01-24
      • 2021-10-15
      • 2018-01-06
      • 1970-01-01
      相关资源
      最近更新 更多