原文地址:daveceddia.com/useeffect-v…
useEffect和useLayoutEffect,是两个工作方式很相似的React Hook。
你可以像下面这样使用它们:
useEffect(() => {
// 执行副作用
return () => { /* clean up */}
}, [dependency, arr])
useLayoutEffect(() => {
// 执行副作用
return () => { /* clean up */}
}, [dependency, array])
但是它们不是完全一样的。通过阅读下面的内容,你将知道二者之间有何不同,以及每一种的适用场景。(大部分场景都适用于useEffect)
不同点
一句话总结:二者的不同在于执行时机。
useEffect是在渲染函数执行完成,并绘制到屏幕之后,再异步执行。
大概流程如下:
- 触发渲染函数执行(改变状态,或者父组件重新渲染)
- React调用组件的渲染函数
- 屏幕中重绘完成
- 执行useEffect
useLayoutEffect,是在渲染函数执行之后,但是屏幕重绘前同步执行。(注意:它可能会影响渲染体验)
大概流程如下:
- 触发渲染函数执行(改变状态,或者父组件重新渲染)
- React调用组件的渲染函数
- 执行useLayoutEffect,并且React等待它执行完成
- 屏幕中重绘完成
99%的场景,适用于useEffect
大部分的场景都是通过effect同步一些状态或者props,这些是不需要立即执行的,或者做一些不影响页面显示的事情。
例如请求远程数据,是不需要立即应用结果修改的。 或者设置某个事件监听 或者在弹窗显示/消失的时候,重置某些状态
大部分的场景,useEffect足够了。
什么时候使用useLayoutEffect?
什么样的场景需要使用useLayoutEffect?当你看见的时候,你就知道了。(字面意思)
如果状态更新,导致组件渲染闪烁,这个时候,就应该用useLayoutEffect,例如初次渲染只有部分状态正确,然后立刻使用最终状态重新渲染时,可能会发生上述情况。
让我用一个例子来说明具体情况。
当你点击页面时,状态立即发生改变(value 重置为0),此时会立刻出发组件重新渲染,并重绘到屏幕上,然后effect执行,将value设置为某个随机数,同时再次立刻触发重新渲染。
这样就会导致2次渲染连续发生。
import React, {
useState,
useLayoutEffect
} from 'react'
import ReactDOM from 'react-dom'
const BlinkyRender = () => {
const [value, setValue] = useState(0)
useLayoutEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200)
}
}, [value])
console.log('render', value)
return (
<div onClick={() => setValue(0)}>value: {{value}}</div>
)
}
ReactDOM.render(
<BlinkyRender />,
document.querySelector('#root')
)
/一般情况下,在div上绑定onClick事件是不推荐的(使用button代替),但是这只是一个演示例子。只是想提醒一下/
可以自己尝试用useLayoutEffect和useEffect分别实现看看。 ps:我自己实践了一下,没啥区别,可能是优化了~
相信你已经注意到了,使用useLayoutEffect时,虽然渲染函数执行2次,但是页面重绘只执行一次。而使用useEffect时,渲染函数执行2次,页面也重绘2次,你会看见有一个快速闪烁的0。
我应该使用useEffect还是useLayoutEffect?
大部分场景中,useEffect是正确的选择。如果遇到闪烁的场景,可以换到useLayoutEffect,看一下是否能解决问题。
因为useLayoutEffect是同步执行的,因此会发生阻塞,直到effect执行完成才会进行页面重绘,如果你的effect内部有执行很慢的代码,可能会引起性能问题。