前言
useEffect 与 useLayoutEffect 是两个 Hooks,前者比较常用,后者在一些场景下也会用到,下面说明两者区别和应用场景。
使用
useEffect
和 useLayoutEffect
是React Hooks里用于处理副作用的钩子(Hooks),它们看起来非常相似,但实际上在何时执行副作用上有细微差别,这影响到了它们的使用场景。
useEffect
useEffect
是在组件渲染到屏幕之后异步执行的。这意味着它不会阻塞浏览器的绘制过程,可以用于大多数副作用场景,比如数据获取、订阅或者手动修改DOM当组件更新后等。
- 使用场景: 用于大部分没有严格的执行时序要求的副作用操作。
- 执行时机: 在全部DOM变更完成后,浏览器绘制之后异步执行。
- 影响: 因为是异步执行,不会阻塞页面的可视化渲染。
useLayoutEffect
useLayoutEffect
的执行时机更接近于类组件的 componentDidMount
和 componentDidUpdate
生命周期方法。它在DOM更新完成后同步执行,但在浏览器进行绘制前。
- 使用场景: 适用于需要同步调整布局的副作用操作。如果需要在DOM变化后立即同步执行一些操作(比如读取DOM布局并同步重绘),就可以用
useLayoutEffect
。 - 执行时机: 在所有DOM变更之后同步执行,但在浏览器绘制之前。
- 影响: 因为阻塞了浏览器的绘制,如果执行复杂或长时间运算,可能会导致性能问题或视觉上的卡顿。
区别
- 执行时机:
useEffect
是异步执行,不会阻塞浏览器的绘制;而useLayoutEffect
是同步执行,在所有DOM变更之后立即运行,但这会延迟浏览器的绘制。 - 适用场景: 一般推荐默认使用
useEffect
,只有在涉及到需要在布局渲染阶段同步执行的DOM操作或有严格的顺序要求时,才使用useLayoutEffect
。
由于 useLayoutEffect
会在绘制前执行,如果操作耗时过长,可能会导致用户感觉到卡顿。因此,除非必要,否则建议优先使用 useEffect
。
例子
当然可以。这里我会演示一个简单的例子,其中useLayoutEffect
和useEffect
将有不同的效果。
考虑以下场景:我们有一个要隐藏的消息,并且有一个按钮用于显示这个消息。如果我们使用useLayoutEffect
去更改消息状态,用户界面不会显示任何隐藏-再显示的过渡效果,因为所有的更改都是在浏览器绘制之前发生的。相反,如果我们使用useEffect
,那么可能会短暂看到消息从隐藏状态变为显示状态的过渡,因为useEffect
是在组件渲染和浏览器绘制完成后才执行。
useLayoutEffect的行为
import React, { useLayoutEffect, useState } from 'react';
function LayoutEffectDemo() {
const [message, setMessage] = useState('这是一条消息');
const [hidden, setHidden] = useState(true);
useLayoutEffect(() => {
if (hidden) {
setMessage('这是一条消息');
}
}, [hidden]);
return (
<div>
{!hidden && <p>{message}</p>}
<button onClick={() => setHidden(false)}>显示信息</button>
</div>
);
}
在这个例子中,“这是一条消息”将会在按钮点击后立即正确显示,用户不会看到任何视觉变化之前消息的状态。
useEffect的行为
现在,我们用相同的例子但用useEffect
来替代useLayoutEffect
:
import React, { useEffect, useState } from 'react';
function EffectDemo() {
const [message, setMessage] = useState('');
const [hidden, setHidden] = useState(true);
useEffect(() => {
if (hidden) {
setMessage('这是一条消息');
}
}, [hidden]);
return (
<div>
{!hidden && <p>{message}</p>}
<button onClick={() => setHidden(false)}>显示信息</button>
</div>
);
}
在使用useEffect
的版本中,按钮点击后状态更新会导致组件的一个渲染周期,在这个周期中消息是隐藏的。然后useEffect
会在这个渲染周期之后运行,并且消息会变成可见的。这个过程虽然非常快,但理论上可以被用户观察到,这就是为什么有的时候你会看到界面闪烁。
每一个钩子都有其用例,而显示不必要的用户界面变化(闪烁等)是应该避免的,通常是通过useLayoutEffect
来处理,因为它在所有DOM变动之后同步执行,而且在浏览器进行绘制之前,从而保证用户看不到中间状态。