如果组件中有
useLayoutEffect
和 useEffect
并且它们都更新组件的状态,那么 useEffect
更新将被推迟,直到 useLayoutEffect
停止触发渲染。
示例
export const App = () => {
const [done, setDone] = useState(false);
const [counter, setCounter] = useState(0);
useLayoutEffect(() => {
if (counter < 20) {
setCounter(counter + 1);
}
}); // intentionally run on every render
useEffect(() => {
setDone(true);
}); // intentionally run on every render
console.log("render done", done);
console.log("render counter", counter);
console.log("\n");
return <div>{counter} renders</div>;
};
给出输出
仅当我们停止在render done false render counter 0 render done false render counter 1 render done false render counter 2 ... render done false render counter 20 render done true render counter 20
done
内触发渲染后,
useLayoutEffect
状态才会更新。
CodeSandbox:https://codesandbox.io/s/github/Andronomewalka/rec_render
GitHub 存储库:https://github.com/Andronomewalka/rec_render
提供的代码结果为
render done false render counter 0 render done true render counter 1 ...
因此,来自
useEffect
和 useLayoutEffect
的状态更新应用于同一渲染。
我在 Bug 中发现了与 useSyncExternalStore 类似的问题:useEffect 中的 setState 在 React 18 #25593 线程中不可靠。
我的问题是:在React 18中是否会发生这种情况,因为
useLayoutEffect
内部的代码以及从中安排的所有状态更新都会阻止浏览器重新绘制屏幕,同时useEffect
内部的状态更新在浏览器之后处理重新绘制屏幕?
那么这样一来,
useLayoutEffect
的更新会延迟useEffect
的更新吗?
在 React 17 中它以另一种方式工作?
https://beta.reactjs.org/reference/react/useLayoutEffect#caveats https://beta.reactjs.org/reference/react/useEffect#caveats
tldr;
done
状态在 useLayoutEffect
完成之后才更新的原因是因为在大多数情况下 useEffect()
在屏幕渲染(绘制) 之后运行 并且 useLayoutEffect()
会阻止它。
@TusharShahi 在评论中提出了一个公平的问题 - 这篇文章 应该澄清 18 中的更改。人们可以使用
flushSync()
来强制重新渲染。请注意,出于性能原因,文档中的所有地方都建议避免使用此技术。对于简单的事情就可以了。
详情:
useLayoutEffect
文档:
内的代码以及安排的所有状态更新 它阻止浏览器重新绘制屏幕。useLayoutEffect
其他说明来自该页面上的示例。
来自 useLayoutEffect 与 useEffect 示例 1:
React 保证
内的代码以及任何状态 其中计划的更新将在浏览器之前处理 重新绘制屏幕。useLayoutEffect
这是文档所说的关于
useEffect()
:
如果您的 Effect 不是由交互(如点击)引起的,React 让浏览器在运行之前先绘制更新的屏幕 你的效果。如果您的效果正在做一些视觉效果(例如, 定位工具提示),并且延迟很明显(例如, 闪烁),需要将 useEffect 替换为 useLayoutEffect。
即使您的效果是由交互(例如点击)引起的, 浏览器可能会在处理状态更新之前重新绘制屏幕 在你的效果里面。通常,这就是您想要的。但是,如果您 必须阻止浏览器重新绘制屏幕,您需要替换 useEffect 和 useLayoutEffect。