Реагирует на устаревшее значение useEffect внутри функции - PullRequest
1 голос
/ 21 марта 2020

Как можно обновить значение переменной simulationOn внутри функции executeSimulation в следующем контексте:

Приложение this.state.simulationOn изменяется с помощью внешнего кода -> ... - > Реакция компонента без сохранения состояния (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]);

    }

В приведенном выше примере simulationOn никогда не меняется на false, хотя useEffect вызывается с обновленным значением (я проверяю с console.log). Я подозреваю, что это потому, что новое значение simulationOn никогда не передается в область действия функции executeSimulation, но я не знаю , как передавать новые значения хука внутри функции executeSimulation.

1 Ответ

0 голосов
/ 21 марта 2020

Функция 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 в executeSimulation вы должны просто запустить executeSimulation, когда simulationOn истинно, и clearTimeout в функции очистки useEffect:

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'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
...