我有React应用程序,它使用React上下文管理状态。我已经创建了简单的counter incrementation reproduction。
有两个上下文。一个用于存储状态,第二个用于调度。这是从article中提取的一种模式。
状态上下文仅存储一个数字,并且只能对此状态调用一个动作:
function reducer(state: State, action: Action): State {
switch (action.type) {
case "inc": {
return state + 1;
}
}
}
我还有两个辅助函数,用于同步和异步增加值:
async function asyncInc(dispatch: React.Dispatch<Action>) {
await delay(1000);
dispatch({ type: "inc" });
dispatch({ type: "inc" });
}
function syncInc(dispatch: React.Dispatch<Action>) {
dispatch({ type: "inc" });
dispatch({ type: "inc" });
}
这是在组件中使用它的方式:
const counter = useCounterState();
const dispatch = useCounterDispatch();
return (
<React.Fragment>
<button onClick={() => asyncInc(dispatch)}>async increment</button>
<button onClick={() => syncInc(dispatch)}>sync increment</button>
<div>{counter}</div>
</React.Fragment>
);
现在,当我单击sync increment
按钮时,一切都会按预期进行。它将调用inc
操作两次,将计数器增加2,并且仅执行一次组件的重新渲染。
当我单击async increment
按钮时,它将首先等待一秒钟并执行inc
操作两次,但是它将重新渲染组件两次。
您必须打开控制台才能查看来自渲染组件的日志。
我有点理解为什么。当它是同步操作时,所有事情都在组件渲染期间发生,因此它将首先操纵状态并在最后渲染。当它是异步操作时,它将首先渲染组件,一秒钟后它将更新状态,一旦触发重新渲染,它将第二次更新,从而触发下一次重新渲染。
那么有可能仅执行一次重新渲染就执行异步状态更新吗?在相同的复制中,有类似的示例,但是使用React.useState时也存在相同的问题。
可以,我们以某种方式批量更新吗?还是我必须创建另一个动作,以便一次对状态执行几项操作?
我还创建了this reproduction,该问题可以通过采取一系列措施来解决此问题,但我很好奇是否可以避免。
答案 0 :(得分:2)
基本上,您为syncInc()
看到的点击事件为batching。因此,您只能看到它呈现一次。
React 批处理在React事件处理程序中完成的所有
setStates
,并在退出其自己的浏览器事件处理程序之前应用它们。
对于您的asyncInc()
,它不在事件处理程序的范围内(由于async
),因此,预计您将获得两次重新渲染(即不进行批处理状态更新)。 / p>
我们可以批量更新吗?
是的,React可以在异步函数中batch updates。
答案 1 :(得分:1)
除非这会导致性能问题,否则我建议不要担心其他渲染。我发现this post about fixing slow renders before worrying about re-renders对这个主题有帮助。
但是,React看起来确实具有不稳定 (因此不应使用) API,用于批量更新ReactDOM.unstable_batchedUpdates
。但是,如果删除或更改它,将来可能会引起头痛。但是要回答您的问题“我们能以某种方式批量更新吗?” ,是的。
const asyncInc = async () => {
await delay(1000);
ReactDOM.unstable_batchedUpdates(() => {
setCounter(counter + 1);
setCounter(counter + 1);
});
};