javascript - 分阶段设置状态反应
问题描述
我试图通过定义一系列“步骤”/“动作”来设置组件的状态。这些动作是定义状态应该如何改变的对象。我想遍历一组动作对象来更新我的组件状态并在 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 />
参数用于定义count
和players
状态应该如何改变。这里{takeFrom: "player1", amount: 5}
的意思是players
state 应该从 key 中移除 5 个(points)player1
,并且更新count
state 到 add 5
。我只想5
从玩家那里拿分,如果他们有这 5 分,如果他们少于 5 分,我会拿他们剩下的并将其添加到count
. 循环内的上述逻辑for
试图实现这一点。
问题是对象似乎有一个闭包players
,所以每次我的 for 循环迭代时,它都在访问原始players
状态,而不是更新后的状态,这会导致玩家点变成负值。
我不确定如何解决这个问题,也不确定是否有更好的方法来解决这个问题。我已经读过不应该在 for 循环中设置状态,所以如果有更好的方法来实现这一点,我也会欣赏这些想法。
解决方案
我已将您的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
如果您需要进一步的支持,请告诉我。
推荐阅读
- python - Pandas 用其他列中的值替换无效的日期时间值
- ios - 是否可以仅与白名单域限制 iOS 应用程序通信?
- kubernetes - 如何在 k8s 部署中将文件挂载到同一子路径中?
- python - 将 bash 变量传递到 python 脚本时出现 TypeError
- mysql - 在 HAVING 子句中使用无聚合条件可能效率低下。考虑将它们移动到 WHERE
- python - 如何迭代构建 F.when().otherwise 逻辑
- build - 令人困惑的错误构建程序:未定义对“QuaZipNewInfo ...”的引用
- c# - Automapper:将多个源数组自定义映射到单个目标数组
- powershell - PowerShell:窗口服务必须停止才能继续
- r - 一列中有多少值出现在另一个R中