【问题标题】:Handling state for a dynamic list of functional components that need to set their own state处理需要设置自己状态的功能组件的动态列表的状态
【发布时间】:2020-11-06 23:35:51
【问题描述】:

我正在用 React 创建一个小 RPG。

目前,我有一个带有 useWalk 钩子的玩家,他们将位置传递给该钩子,并且钩子手柄会在他们四处移动时改变它们的位置。

export const Player: React.FC<PlayerProps> = ({ skin, positionIsWalkable}) => {
  const [position, setPosition] = useState<Position2D>({x: 8, y: 8});
  const { direction, walk } = useWalk(positionIsWalkable, position, setPosition);

这很好用。然后我添加了一个需要知道玩家在哪里的 AI 代理,因此我将玩家的位置状态拉到了 App 级别,这样我就可以创建一个“findClosestPlayer”方法,并将其传递给我的 AI。

现在我想添加多个玩家,但事情变得很棘手...... 这样做真的很方便:


//this would need to be wrapped in useState/useRef to avoid reevaluations every render, but let's ignore that
const playerPositions: Array<[Position2D, Dispatch<SetStateAction<Position2D>>]> = [];

  for (let i = 0; i < 1; i++) {
    playerPositions.push(
      useState<Position2D>({ x: i, y: i })
    );
  }

return (
      {playerPositions.map((positionTuple) => (
        <Player
          position={positionTuple[0]}
          setPosition={positionTuple[1]}
          skin={PlayerSprite}
          positionIsWalkable={positionIsWalkable}
        />
      ))}
)

但你不能在循环中使用 useState。

我探索过做类似的事情:

  const [playerPositions, setPlayerPositions] = useState<Position2D[]>([]);

  const setPlayerPosition = useCallback((playerIndex: number) => (pos: Position2D) => {
    setPlayerPositions(prev => {
      let copy = prev.slice();
      copy[playerIndex] = pos;
      return copy;
    })
  },[]);

但是 setPlayerPosition(playerIndex) 的类型是 (pos: Position2D) => void 而不是 Dispatch,这会阻止我在播放器 && useWalk 中执行 setPosition(prev=> {...}) 之类的操作,更不用说 useWalk 期待 Dispatch 功能道具,因为它被其他代理使用,然后只是我的播放器。

有什么想法吗?我理想的解决方案是为每个玩家提供一个 [Position2D, Dispatch] 元组,但我愿意接受其他建议。

我的目标是完全在 React 中创建它作为学习目标(这是我第一次在 React 中工作),但也许这种域状态管理真的最好留给 Redux 之类的东西(我还需要这样做)学习)。但如果我能做到的话,我很想在重构和合并 Redux 之类的东西之前完全在 React 中完成这个项目。

【问题讨论】:

    标签: reactjs typescript react-hooks use-state


    【解决方案1】:

    在一个非常基本的层面上,您基本上可以将单个玩家的动作映射为像普通的 setPosition 函数一样工作 - 使其与更新状态的正常和函数方式一起工作,只需使其适用于单个玩家一次。

    要变得更复杂,但总体上可能是更好的解决方案,我建议使用context api 来存储您的所有状态。这样您就不必在整个应用程序中传递各个状态。您也可以将它与它的工作方式结合起来 - 这样您的更下方的组件可以通过上下文访问玩家位置。

    你也可以useReducer设置更复杂的逻辑和动作来做你想要的玩家位置,然后你可以传递调度。

    还有一个相当新的 Recoil 库,它能够比 redux 进行更多的动态访问,尽管它仍然是相当新的,我还没有机会使用它。

    const numPlayers = 4;
    const [playerPositions, setAllPlayerPositions] = useState<Position2D[]>(() => {
      return Array(numPlayers)
        .fill(null)
        .map((_val, index) => ({ x: index, y: index }));
    });
    const setPlayerPositions = useMemo<
      React.Dispatch<React.SetStateAction<Position2D>>[]
    >(() => {
      const setters = Array(numPlayers)
        .fill(null)
        .map<React.Dispatch<React.SetStateAction<Position2D>>>(
          (_val, index) => (value) => {
            setAllPlayerPositions((prevValue) => {
              const newSubValue =
                typeof value === 'function' ? value(prevValue[index]) : value;
              if (newSubValue !== prevValue[index]) {
                return prevValue;
              }
              const newValue = prevValue.slice();
              newValue.splice(index, 1, newSubValue);
              return newValue;
            });
          }
        );
      return setters;
    }, [numPlayers]);
    
    return (
      <>
        {playerPositions.map((position, index) => (
          <Player
            position={position}
            setPosition={setPlayerPositions[index]}
            skin={PlayerSprite}
            positionIsWalkable={positionIsWalkable}
          />
        ))}
      </>
    );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-09
      • 2018-09-29
      • 2020-07-09
      • 1970-01-01
      • 2021-04-17
      • 2020-05-07
      • 1970-01-01
      相关资源
      最近更新 更多