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

Cannot access updated data from useReducer hook in function defined in setTimeout

在我的应用程序中,我在单击按钮时使用来自 useReducer 钩子的调度,并且在同一函数中我使用 2 秒的 setTimeout。但是当我使用 usereducer 的调度存储数据时,我没有在 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 记录新的调度值,因为您看不到未来。由于状态更改,您只能在组件 re-render 之后记录新数据。那么你可以而且应该使用

    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 的引用。结合外部状态引用的函数称为 closure 并且此闭包确保值 state.data 在函数存在期间一直保持活动状态(不被垃圾收集器收集)。

在第二行中 setTimeout 是用这个函数调用的。简化这意味着一个任务存储在某个地方,它指出,这个函数必须在给定的超时后执行。因此,只要此任务未完成,该函数就会保持活动状态,并且变量 state.data.

与此同时,在处理任务之前很长时间,dispatched 新动作,计算新状态并 Demo re-rendered。这样就创建了一个新的 state.data 和一个新的 handleClick。随着 handleClick 的新创建,也创建了一个传递给 setTimeout 的新函数。然后将整个 handleClick 函数作为 onClick 处理程序传递给 button 元素。

re-render现在已经结束了,但是之前的任务还没有完成。现在,当超时持续时间结束时(在 re-rendering 组件很久之后),任务将从任务队列中取出并执行。该任务仍然引用之前渲染中的函数,并且该函数仍然引用之前渲染中的值 state.data。所以记录到控制台的值仍然是之前渲染的旧值。