首页 > 解决方案 > 分阶段设置状态反应

问题描述

我试图通过定义一系列“步骤”/“动作”来设置组件的状态。这些动作是定义状态应该如何改变的对象。我想遍历一组动作对象来更新我的组件状态并在 UI 中反映这些更改。我也在努力让这些动作彼此相隔大约 1 秒。

我做了下面的例子来说明我正在尝试做的事情和问题:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>

<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script type="text/babel">

const {useState} = React;

const sleep = ms => new Promise(res => setTimeout(res, ms));
const App = ({actions}) => {
  const [players, setPlayers] = useState({player1: 20, player2: 20});
  const [count, setCount] = useState(0);
  
  const handleActions = async () => {
    for(const {takeFrom, amount} of actions) {
      await sleep(1000); // wait 1 second
      console.log(takeFrom, amount, players[takeFrom]); // for debugging (check console)

      // only take what's available so player doesn't have a negative value (0 is min value)
      const maxAmount = Math.min(amount, players[takeFrom]); 

      setCount(currCount => currCount + maxAmount); // pool taken values together
      setPlayers((players) => ({
        ...players, 
        [takeFrom]: players[takeFrom] - maxAmount
      }))
    }
  }
  return <div>
    <p>Count: {count}</p>
    <ul>
      {Object.entries(players).map(([playerId, points]) => 
        <li key={playerId}>{playerId}: {points}</li>
      )}
    </ul>
    <button onClick={handleActions}>Perform actions</button>
  </div>;
}

ReactDOM.render(<App actions={[
  {takeFrom: "player1", amount: 5}, // now player1 has 15 points (20 - 5 )✅
  {takeFrom: "player2", amount: 10}, // now player 2 has 10 points ✅
  {takeFrom: "player1", amount: 20} // now player1 has 0 points (cap the amount) ❌ 
]} />, document.body);

</script>

actions传递给组件的<App />参数用于定义countplayers状态应该如何改变。这里{takeFrom: "player1", amount: 5}的意思是playersstate 应该从 key 中移除 5 个(points)player1,并且更新countstate 到 add 5。我只想5从玩家那里拿分,如果他们有这 5 分,如果他们少于 5 分,我会拿他们剩下的并将其添加到count. 循环内的上述逻辑for试图实现这一点。

问题是对象似乎有一个闭包players,所以每次我的 for 循环迭代时,它都在访问原始players状态,而不是更新后的状态,这会导致玩家点变成负值。

我不确定如何解决这个问题,也不确定是否有更好的方法来解决这个问题。我已经读过不应该在 for 循环中设置状态,所以如果有更好的方法来实现这一点,我也会欣赏这些想法。

标签: javascriptreactjsstate

解决方案


我已将您的handleActions功能更新如下。

const handleActions = () => {
  let processPlayer = players; // To store the old version of the players
  let finalCount = count; // To store the old version of the count

  actions.forEach((player) => {
    const count = processPlayer[player.takeFrom] - player.amount;
    const maxAmount = Math.min(player.amount, processPlayer[player.takeFrom]);

    finalCount += maxAmount;
    processPlayer[player.takeFrom] = count >= 0 ? count : 0;
  }); // Process them as per the requirement

  setPlayers({
    player1: processPlayer.player1,
    player2: processPlayer.player2
  }); // Merge the result with the players state

  setCount(finalCount); // Merge the result with the count state
};

将播放器详细信息和计数存储在单独的变量中,并在函数结束时将它们与应用程序状态合并。这里状态只更新一次。

https://codesandbox.io/s/actions-loop-68139806-lbsxs

如果您需要进一步的支持,请告诉我。


推荐阅读