useEffect和useLayoutEffect的区别

119 阅读2分钟

useLayoutEffectuseEffect 的结构、功能相同,区别只是它们在事件循环中的调用时机不同

useEffect

useEffect 的回调函数是【异步宏任务】,在下一轮事件循环才会执行。根据 JS 线程与 GUI 渲染线程互斥原则,在 JS 中页面的渲染线程需要当前事件循环的宏任务与微任务都执行完,才会执行渲染线程,渲染页面后,退出渲染线程,控制权交给 JS 线程,再执行下一轮事件循环。

  1. 好处:这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因为绝大多数操作不应阻塞浏览器对屏幕的渲染更新。
  2. 坏处:产生二次渲染问题,第一次渲染的是旧的状态,接着下一个事件循环中,执行改变状态的函数,组件又携带新的状态渲染,在视觉上,就是二次渲染。
import React, { useEffect, useState } from 'react'

const Index: React.FC = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    setCount(2)
    console.log('useEffect 内部', count)
  })

  return <div>{(console.log('渲染 JSX', count), count)}</div>
}

export default Index

下面 gif 图,先渲染数字 1,再渲染数字 2,即二次渲染

useLayoutEffect

而 useLayoutEffect 与 componentDidMount、componentDidUpdate 生命周期钩子是【异步微任务】,在渲染线程被调用之前就执行。这意味着回调内部执行完才会更新渲染页面,没有二次渲染问题。

  1. 好处:没有二次渲染问题,页面视觉行为一致。
  2. 坏处:在回调内部有一些运行耗时很长的代码或者循环时,页面因为需要等 JS 执行完之后才会交给渲染线程绘制页面,等待时期就是白屏效果,即阻塞了渲染。
import React, { useLayoutEffect, useState } from 'react'

const Index: React.FC = () => {
  const [count, setCount] = useState(0)
  useLayoutEffect(() => {
    setCount(2)
    console.log('useEffect 内部', count)
  })

  return <div>{(console.log('渲染 JSX', count), count)}</div>
}

export default Index

下面 gif 图,并没有渲染数字 1,而是直接渲染数字 2

总结

  1. useEffect 是异步非阻塞,useLayoutEffect 是同步阻塞。
  2. useEffect 是在浏览器绘制之后执行,useLayoutEffect 是在 DOM 变更之后,浏览器绘制前执行
  3. useLayoutEffectcomponentDidMount是等价的,会同步调用,阻塞渲染
  4. 会影响到渲染的操作尽量放到 useLayoutEffect中去,避免出现闪烁问题
  5. 优先使用 useEffect,因为它是异步执行的,不会阻塞渲染
  6. useLayoutEffect 会阻塞 DOM 的渲染,避免过度使用