无法从setTimeout中定义的函数中的useReducer钩子访问更新的数据



在我的应用程序中,我在单击按钮时使用useReducer钩子的调度,在相同的函数中,我使用2秒的setTimeout。但当我使用usereducer的dispatch存储数据时,我不会在setTimeout函数中获得更新的值。

我不能分享原始代码,但分享了另一个出现此问题的演示应用程序的片段。

const initialData = { data: "ABC" };
function reducer(state = initialData, action) {
switch (action.type) {
case "STORE":
return {
...state,
data: action.payload
};
default:
return state;
break;
}
}
function Demo() {
const [state, dispatch] = React.useReducer(reducer, initialData);
console.log("Render : ",state.data);  //Will be executed on each rendering
const handleClick = () => {
dispatch({
type: "STORE",
payload: state.data + parseInt(Math.random() * 10)
});
setTimeout(() => {
console.log("ButtonClick : ",state.data); //Will be executed after 2 seconds of dispatching.
}, 2000);
};
return <button onClick={handleClick}>{state.data}</button>;
}
ReactDOM.render(<Demo />, document.getElementById("app"));

在上面的例子中,我使用dispatch将数据存储在reducer中,并且在2秒钟后在Button Click上调用console.log("ButtonClick"(,但即使在2秒钟之后,我也不会在控制台中获得更新的数据。但在console.log("Render"(中,我得到了更新的数据。

现场示例:https://codepen.io/aadi-git/pen/yLJLmNa

当您调用时

const handleClick = () => {
dispatch({
type: "STORE",
payload: state.data + parseInt(Math.random() * 10)
});
setTimeout(() => {
console.log("ButtonClick : ",state.data); //Will be executed after 2 seconds of dispatching.
}, 2000);
};

这就是发生的情况:

  1. 使用对象运行dispatch以存储一些数据。此函数是异步执行的,因此无法立即获得结果。

  2. 注册一个超时处理程序,它将state.data的当前值记录到控制台。由于前面的dispatch仍在进行中,因此state.data的值仍然是旧值。

    这意味着您不能通过在dispatch调用后运行console.log来记录新的调度值,因为您看不到未来。由于状态发生变化,只能在组件重新渲染后记录新数据。那么你可以也应该使用

    React.useEffect(() => {
    console.log(state.data);
    }, [state.data]);
    

关于setTimeout以及为什么console.log在其中记录旧值的更多解释

您使用

setTimeout(() => {
console.log("ButtonClick : ", state.data);
}, 2000);

这相当于

const callback = () => console.log("ButtonClick : ", state.data);
setTimeout(callback, 2000);

在第一行中,您创建了一个函数(此处命名为callback(,用于打印一些文本。该文本由一个简单字符串和state.data的值组成。因此,此函数具有对变量state.data引用。与对外部状态的引用相结合的函数称为闭包,此闭包确保值state.data在函数存在期间保持活动状态(不被垃圾收集器装仓(。

在第二行中,使用该函数调用CCD_ 13。简化了,这意味着一个任务存储在某个地方,它表明,该函数必须在给定的超时后执行。因此,只要这个任务没有完成,函数就会保持活动状态,并且伴随它的还有变量state.data

同时,在处理任务之前很久,新动作被dispatched,计算新状态并重新呈现Demo。由此产生一个新的CCD_ 17和一个新CCD_。随着CCD_ 19的新创建,还创建了传递给CCD_ 20的新函数。然后将整个handleClick函数作为button元素的onClick处理程序传递。

重新渲染现在已经结束,但之前的任务仍处于挂起状态。现在,当超时持续时间结束时(在重新渲染组件之后很长一段时间(,任务将从任务队列中取出并执行。任务仍然引用了之前渲染中的函数,并且此函数仍然引用了以前渲染中的值state.data。因此,记录到控制台的值仍然是以前渲染的旧值。

最新更新