什么时候使用useLayoutEffect代替useEffect?

6,993 阅读3分钟

原文地址:daveceddia.com/useeffect-v…

useEffect和useLayoutEffect,是两个工作方式很相似的React Hook。

你可以像下面这样使用它们:

useEffect(() => {
	// 执行副作用
	return () => { /* clean up */}
}, [dependency, arr])

useLayoutEffect(() => {
	// 执行副作用
	return () => { /* clean up */}
}, [dependency, array])

但是它们不是完全一样的。通过阅读下面的内容,你将知道二者之间有何不同,以及每一种的适用场景。(大部分场景都适用于useEffect)

不同点

一句话总结:二者的不同在于执行时机。

useEffect是在渲染函数执行完成,并绘制到屏幕之后,再异步执行。

大概流程如下:

  1. 触发渲染函数执行(改变状态,或者父组件重新渲染)
  2. React调用组件的渲染函数
  3. 屏幕中重绘完成
  4. 执行useEffect

useLayoutEffect,是在渲染函数执行之后,但是屏幕重绘前同步执行。(注意:它可能会影响渲染体验)

大概流程如下:

  1. 触发渲染函数执行(改变状态,或者父组件重新渲染)
  2. React调用组件的渲染函数
  3. 执行useLayoutEffect,并且React等待它执行完成
  4. 屏幕中重绘完成

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代替),但是这只是一个演示例子。只是想提醒一下/

可以自己尝试用useLayoutEffectuseEffect分别实现看看。 ps:我自己实践了一下,没啥区别,可能是优化了~

相信你已经注意到了,使用useLayoutEffect时,虽然渲染函数执行2次,但是页面重绘只执行一次。而使用useEffect时,渲染函数执行2次,页面也重绘2次,你会看见有一个快速闪烁的0。

我应该使用useEffect还是useLayoutEffect?

大部分场景中,useEffect是正确的选择。如果遇到闪烁的场景,可以换到useLayoutEffect,看一下是否能解决问题。

因为useLayoutEffect是同步执行的,因此会发生阻塞,直到effect执行完成才会进行页面重绘,如果你的effect内部有执行很慢的代码,可能会引起性能问题。