reactjs - useEffect 中的更新状态显示警告
问题描述
我想在更新其他状态后更新一个状态:
export default function App() {
const [diceNumber, setDiceNumber] = useState(0);
const [rolledValues, setRolledValues] = useState([
{ id: 1, total: 0 },
{ id: 2, total: 0 },
{ id: 3, total: 0 },
{ id: 4, total: 0 },
{ id: 5, total: 0 },
{ id: 6, total: 0 }
]);
const rollDice = async () => {
await startRolingSequence();
};
const startRolingSequence = () => {
return new Promise(resolve => {
for (let i = 0; i < 2500; i++) {
setTimeout(() => {
const num = Math.ceil(Math.random() * 6);
setDiceNumber(num);
}, (i *= 1.1));
}
setTimeout(resolve, 2600);
});
};
useEffect(()=>{
if(!diceNumber) return;
const valueIdx = rolledValues.findIndex(val => val.id === diceNumber);
const newValue = rolledValues[valueIdx];
const {total} = newValue;
newValue.total = total + 1;
setRolledValues([
...rolledValues.slice(0,valueIdx),
newValue,
...rolledValues.slice(valueIdx+1)
])
}, [diceNumber]);
return (
<div className="App">
<button onClick={rollDice}>Roll the dice</button>
<div> Dice Number: {diceNumber ? diceNumber : ''}</div>
</div>
);
}
这是一个沙箱
当用户掷骰子时,几个 setTimeouts 将改变状态值并最终解决。一旦解决,我想跟踪一组对象中的分数。
所以当我这样写它时,它可以工作,但 eslint 给了我一个缺少依赖项的警告。但是当我放入依赖项时,useEffect 将在一个永远循环中结束。
如何在状态更新后实现状态更新而不会导致永远循环?
解决方案
这是一种设置方法,可以保持使用效果并且在效果中没有依赖问题:https ://codesandbox.io/s/priceless-keldysh-wf8cp?file=/src/App.js
setRolledValues 调用逻辑的这种变化包括消除rolledValues 数组的意外突变,如果您在其他地方使用它可能会导致问题,因为所有React 状态都应该不可变地使用以防止出现问题。
setRolledValues 已更改为使用状态回调选项来防止依赖要求。
useEffect(() => {
if (!diceNumber) return;
setRolledValues(rolledValues => {
const valueIdx = rolledValues.findIndex(val => val.id === diceNumber);
const value = rolledValues[valueIdx];
const { total } = value;
return [
...rolledValues.slice(0, valueIdx),
{ ...value, total: total + 1 },
...rolledValues.slice(valueIdx + 1)
];
});
}, [diceNumber]);
我不建议像这样使用它,尽管它存在一个问题,即如果连续多次滚动相同的数字,效果只会在第一次触发。
您可以将逻辑移到 rollDice 回调中,这将消除正在发生的这两个问题。https://codesandbox.io/s/stoic-visvesvaraya-402o1?file=/src/App.js
我在 rollDice 周围添加了一个 useCallback 以确保它不会更改引用,因此它可以在 useEffects 中使用。
const rollDice = useCallback(() => {
const num = Math.ceil(Math.random() * 6);
setDiceNumber(num);
setRolledValues(rolledValues => {
const valueIdx = rolledValues.findIndex(val => val.id === num);
const value = rolledValues[valueIdx];
const { total } = value;
return [
...rolledValues.slice(0, valueIdx),
// newValue,
{ ...value, total: total + 1 },
...rolledValues.slice(valueIdx + 1)
];
});
}, []);
推荐阅读
- oracle - 在 where 子句中测试时,输入值对于日期格式来说不够长
- android - 用于扫描的 zxing 相机视图是颠倒的
- laravel - laravel Echo --> 无法读取未定义的属性“通道”
- ios - 返回到不是初始 ViewController 的 MainMenu ViewController - Xamarin iOS
- r - R循环引用多个数据帧
- arrays - 如何检查数组是否包含某个值?
- html - 在 IE 中打开时无法显示聊天机器人 iframe(MS btoframework)中的所有选项
- laravel - Laravel groupBy 值并以 groupBy 值为 key 存储另一个值的总和
- excel - 当用户在对话框中写入要删除的条目的名称时,如何删除组合框列表中的特定项目
- javascript - 使用 javascript 仅查找和替换表中过滤的行