对useEffect 钩子和useLayoutEffect 钩子感到困惑?
在这篇文章中,我们将研究useEffect 和useLayoutEffect有什么不同以及它们的使用情况。
在我们深入了解两者的区别之前,值得一提的是,这两个钩子------------------都是
- 处理React中的副作用
- 有一个相同的签名
让我们考虑一个计数器的简单例子。
import { useEffect, useState, useLayoutEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('useEffect is fired');
//Side effect
}, [count]);
return(
<div>
<h1>Count: {count} </h1>
<button onClick={() => setCount(count + 1)}>Increment count</button>
</div>
)
}
当组件被装入Count: {count} ,屏幕上就会出现。当按钮被点击时,计数器就会递增。
当useLayoutEffect被替换成useEffect 时,也会出现同样的行为。
useEffect 与useLayoutEffect 有什么不同?
它们之间的区别在于其调用的时间。
这张图将帮助我们更好地理解这个流程。

图片来源 : 钩子流程
useEffect 在渲染器被绘制到屏幕上之后异步运行,解除了浏览器绘制过程的阻塞。
所以这看起来像。
- 我们以某种方式引起渲染(通过状态,或父级重新渲染或上下文变化)。 在我们的例子中,通过点击增量按钮。
- React内部更新状态。计数器的值被更新为1。
- React处理DOM的突变。
<h1>Count: 0 </h1>被改变为<h1>Count: 1 </h1> - 浏览器将这个DOM变化画在浏览器的屏幕上。在我们的例子中,
Count: 1被画在屏幕上。 useEffect函数在浏览器绘制完DOM变化后被触发。 打印useEffect is fired。
然而,useLayoutEffect 在所有DOM突变之后同步触发。
所以这看起来像。
- 我们以某种方式引起渲染(通过状态,或父级重新渲染或上下文变化)。 在我们的例子中,通过点击增量按钮。
- React内部更新状态。计数器的值被更新为1。
- React处理DOM的突变。
<h1>Count: 0 </h1>被改变为<h1>Count: 1 </h1> useLayoutEffect是在DOM突变后立即触发的。- 浏览器会把这个DOM变化画在浏览器的屏幕上。在我们的例子中,
Count: 1,被画在屏幕上。
简单地说,useLayoutEffect并不关心浏览器是否已经绘制了DOM变化。 它在DOM变化被计算出来后立即触发这个函数。
useEffect 的用例和useLayoutEffect
决定在useEffect和useLayoutEffect之间选择哪个钩子可能很棘手。
了解情况是关键!
99%的情况下,useEffect 是我们想要使用的。大多数时候,我们是在获取数据和设置事件处理程序,不需要立即发生。 它也不影响页面外观。对于所有这些情况,我们应该使用useEffect 钩子。
那么,什么是使用useLayoutEffect 的正确时机呢?
如果我们的效果会改变DOM(比如获得滚动位置或某个元素的其他样式)或涉及到动画,请选择使用LayoutEffect而不是useEffect。
原因是。
useEffect的钩子是在屏幕画完之后被调用的。 因此,在画完屏幕后立即再次突变DOM,如果突变被客户端看到,将导致闪烁的效果。
让我们以一个工具提示为例。 在这里,我们从DOM测量button ,并操纵工具提示的位置。
const App = () => {
const [showToolTip, setShowTooltip] = useState(false);
const buttonRef = useRef();
const tooltipRef = useRef();
useEffect(() => {
if (buttonRef.current == null || tooltipRef.current == null) return;
const { left, top } = buttonRef.current.getBoundingClientRect();
tooltipRef.current.style.left = `${left + 120}px`;
tooltipRef.current.style.top = `${top - 20}px`;
}, [showToolTip]);
return (
<div>
<button ref={buttonRef}
onClick={() => setShowTooltip(prevState => !prevState)}>
Toggle tooltip
</button>
{showToolTip &&
(<div ref={tooltipRef}
style=
{{ position: 'absolute',
border: '1px solid gray',
padding: '20px',
backgroundColor: 'lightgray'
}}
>
This is a Tooltip!
</div>)}
</div>
)
};
我们看到在按钮被点击后有一小段时间的闪烁。 这是因为useEffect是异步触发的。

用useLayoutEffect替换useEffect可以消除闪烁。

React 18中的useEffect
在React 18中运行同样的useEffect代码不会导致闪烁。
是的,这是真的!
在React 18中,当useEffect是离散输入的结果时,它就会同步发射。
现在,什么是离散输入?
离散输入是一种事件类型,一个事件的结果可以影响下一个事件的行为,比如点击。
在我们的例子中,点击按钮,同步地启动useEffect,避免了闪烁。
要想了解React 18中useEffect的变化,请查看这个链接。
结束语
- 用useLayoutEffect替换useEffect钩子在简单的应用程序中可能不会有明显的效果,强烈建议不要这样做。
- useLayoutEffect的运行成本很高,所以应该只在需要时使用。
- 在React 18中,只有在离散事件中useEffect才会同步启动。 对于大多数情况(非离散事件),React会将效果推迟到绘制之后。
请参考这个链接,了解useLayoutEffect和服务器端渲染的情况。