在函数内反应 useEffect 陈旧值
React useEffect stale value inside function
如何在以下上下文中更新函数 executeSimulation
内部变量 simulationOn
的值:
App 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]);
}
在上面的示例中,simulationOn
永远不会更改为 false
,即使使用更新后的值调用了 useEffect(我检查了 console.log)。我怀疑这是因为 simulationOn
的新值从未传递到函数 executeSimulation
的范围,但我不知道 如何在函数 [=] 内部传递新的挂钩值12=].
executeSimulation 函数有一个陈旧的闭包 simulationOn 永远不会为真,这里是演示陈旧闭包的代码:
var component = test => {
console.log('called Component with',test);
setTimeout(
() => console.log('test in callback:', test),
20
);
}
component(true);
coponent(false)
请注意,Robot
每次渲染时都会被调用,但是 executeSimulation
从之前的渲染中运行,在它的闭包中具有之前的 simulationOn
值(参见上面陈旧的闭包示例)
而不是在 executeSimulation
中检查 simulationOn
你应该在 simulationOn
为真时开始 executeSimulation
并且在 useEffect 的清理函数中使用 clearTimeout:
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>
simulationOn 永远不会改变,因为父组件必须改变它。它在 属性 中传递给此 Robot 组件。
我创建了一个沙箱示例来展示,如果您在父级中正确更改它,它将向下传播。
https://codesandbox.io/s/robot-i85lf.
此机器人存在一些设计问题。似乎假设机器人可以通过将其作为实例变量保存来“记住”索引值。 React 不是那样工作的。此外,它假设 useEffect 将在一个参数更改时恰好被调用一次,这是不正确的。我们不知道 useEffect 会被调用多少次。 React 仅保证在其中一个依赖项发生更改时调用它。但它可以被更频繁地调用。
我的示例显示命令列表必须由父级保存,并且需要发送完整列表,因此哑机器人可以显示它执行的所有命令。
如何在以下上下文中更新函数 executeSimulation
内部变量 simulationOn
的值:
App 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]);
}
在上面的示例中,simulationOn
永远不会更改为 false
,即使使用更新后的值调用了 useEffect(我检查了 console.log)。我怀疑这是因为 simulationOn
的新值从未传递到函数 executeSimulation
的范围,但我不知道 如何在函数 [=] 内部传递新的挂钩值12=].
executeSimulation 函数有一个陈旧的闭包 simulationOn 永远不会为真,这里是演示陈旧闭包的代码:
var component = test => {
console.log('called Component with',test);
setTimeout(
() => console.log('test in callback:', test),
20
);
}
component(true);
coponent(false)
请注意,Robot
每次渲染时都会被调用,但是 executeSimulation
从之前的渲染中运行,在它的闭包中具有之前的 simulationOn
值(参见上面陈旧的闭包示例)
而不是在 executeSimulation
中检查 simulationOn
你应该在 simulationOn
为真时开始 executeSimulation
并且在 useEffect 的清理函数中使用 clearTimeout:
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>
simulationOn 永远不会改变,因为父组件必须改变它。它在 属性 中传递给此 Robot 组件。 我创建了一个沙箱示例来展示,如果您在父级中正确更改它,它将向下传播。 https://codesandbox.io/s/robot-i85lf.
此机器人存在一些设计问题。似乎假设机器人可以通过将其作为实例变量保存来“记住”索引值。 React 不是那样工作的。此外,它假设 useEffect 将在一个参数更改时恰好被调用一次,这是不正确的。我们不知道 useEffect 会被调用多少次。 React 仅保证在其中一个依赖项发生更改时调用它。但它可以被更频繁地调用。
我的示例显示命令列表必须由父级保存,并且需要发送完整列表,因此哑机器人可以显示它执行的所有命令。