在函数内部反应useEffect过时的值

时间:2020-03-21 09:46:57

标签: javascript reactjs sleep staleobjectstate

在以下情况下,如何在函数simulationOn内更新变量executeSimulation的值:

应用this.state.simulationOn通过外部代码更改-> ...->重新渲染React无状态组件(Robot)->用新值调用useEffect钩子- -> executeSimulation不会用新值simulationOn更新。

    function Robot({ simulationOn, alreadyActivated, robotCommands }) {

        useEffect(() => {
            function executeSimulation(index, givenCommmands) {
                index += 1;
                if (index > givenCommmands.length || !simulationOn) {
                    return;
                }
                setTimeout(executeSimulation.bind({}, index, givenCommmands), 1050);
            }
            if (simulationOn && !alreadyActivated) {
                executeSimulation(1, robotCommands);
            }
        }, [simulationOn, alreadyActivated, robotCommands]);

    }

在上面的示例中,即使使用更新后的值调用了useEffect(我使用console.log进行检查),simulationOn也从未更改为false。我怀疑这是因为simulationOn的新值从未传递到函数executeSimulation的范围,但是我不知道如何在函数{{1}内传递新的钩子值}

2 个答案:

答案 0 :(得分:0)

executeSimulation函数具有一个过时的关闭模拟功能永远不会为真,这是演示过时的关闭的代码:

var component = test => {
  console.log('called Component with',test);
  setTimeout(
    () => console.log('test in callback:', test),
    20
  );
}
component(true);
coponent(false)

请注意,Robot每次渲染时都会被调用,但是executeSimulation从前一个渲染开始运行,并且前一个渲染在其闭包中具有先前的simulationOn值(请参见上述过时的闭包示例)

simulationOn为true且useEffect的清除函数中的clearTimeout为true时,您应该仅在executeSimulation中检查executeSimulation

simulationOn
const Component = ({ simulation, steps, reset }) => {
  const [current, setCurrent] = React.useState(0);
  const continueRunning =
    current < steps.length - 1 && simulation;
  //if reset or steps changes then set current index to 0
  React.useEffect(() => setCurrent(0), [reset, steps]);
  React.useEffect(() => {
    let timer;
    function executeSimulation() {
      setCurrent(current => current + 1);
      //set timer for the cleanup to cancel it when simulation changes
      timer = setTimeout(executeSimulation, 1200);
    }
    if (continueRunning) {
      timer = setTimeout(executeSimulation, 1200);
    }
    return () => {
      clearTimeout(timer);
    };
  }, [continueRunning]);
  return (
    <React.Fragment>
      <h1>Step: {steps[current]}</h1>
      <h1>Simulation: {simulation ? 'on' : 'off'}</h1>
      <h1>Current index: {current}</h1>
    </React.Fragment>
  );
};
const App = () => {
  const randomArray = (length = 3, min = 1, max = 100) =>
    [...new Array(length)].map(
      () => Math.floor(Math.random() * (max - min)) + min
    );
  const [simulation, setSimulation] = React.useState(false);
  const [reset, setReset] = React.useState({});
  const [steps, setSteps] = React.useState(randomArray());
  return (
    <div>
      <button onClick={() => setSimulation(s => !s)}>
        {simulation ? 'Pause' : 'Start'} simulation
      </button>
      <button onClick={() => setReset({})}>reset</button>
      <button onClick={() => setSteps(randomArray())}>
        new steps
      </button>
      <Component
        simulation={simulation}
        reset={reset}
        steps={steps}
      />
      <div>Steps: {JSON.stringify(steps)}</div>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));

答案 1 :(得分:0)

simulationOn 永远不会改变,因为父组件必须改变它。它通过一个属性传递给此 Robot 组件。 我创建了一个沙箱示例来显示,如果您在父级中正确更改它,它将向下传播。 https://codesandbox.io/s/robot-i85lf

此机器人存在一些设计问题。似乎假设机器人可以通过将其作为实例变量来“记住”索引值。 React 不是这样工作的。此外,它假设当一个参数改变时 useEffect 将被调用一次,这是不正确的。我们不知道 useEffect 会被调用多少次。 React 仅保证在依赖项之一发生更改时会调用它。但它可以被更频繁地调用。

我的例子显示命令列表必须由父级保存,并且需要发送完整列表,因此哑机器人可以显示它执行的所有命令。