首页 > 解决方案 > 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 将在一个永远循环中结束。

如何在状态更新后实现状态更新而不会导致永远循环?

标签: reactjsreact-hooksuse-effectuse-state

解决方案


这是一种设置方法,可以保持使用效果并且在效果中没有依赖问题: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)
      ];
    });
  }, []);

推荐阅读