useEffect 中的更新状态显示警告

Update state in useEffect shows warning

我想在其他状态更新后更新状态:

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>
  );
}

这是一个sandbox

当用户掷骰子时,几个 setTimeouts 将更改状态值并最终解决。一旦它解决了,我想跟踪一组对象中的分数。

因此,当我这样写时,它可以工作,但 eslint 警告我缺少依赖项。但是当我放入依赖项时,useEffect 将永远循环结束。

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

您应该能够将所有逻辑都放在 setRolledValues

useEffect(() => {
  if (!diceNumber) return;

  setRolledValues((prev) => {
    const valueIdx = prev.findIndex(val => val.id === diceNumber);
    const newValue = prev[valueIdx];
    const { total } = newValue;
    newValue.total = total + 1;
    return [
      ...prev.slice(0, valueIdx),
      newValue,
      ...prev.slice(valueIdx + 1)
    ]
  })

}, [diceNumber]);

编辑:正如其他人指出的那样,useEffect 似乎不适合此应用程序,因为您可以简单地在函数中更新 setRolledValues

如果有某种底层系统我们没有在您必须使用这样的观察者模式的地方显示,您可以更改diceNumber的数据类型改为一个对象,这样对同一号码的后续调用将触发 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)
      ];
    });
  }, []);