嗨,useLayoutEffect,醒醒

6,813 阅读4分钟

前言

React hooks已经使用了近一年的时间,useLayoutEffect就像是个大神一样,静静的在那儿看着,始终找不到用武之地...

Layout布局、设计、陈列的意思。难道是和操作DOM相关?

useLayoutEffect的使用方法,官方文档解释如下:

useLayoutEffect的函数签名与useEffect相同,但是它会在所有的DOM变更之后同步调用effect。可以使用它来读取DOM布局并同步触发重新渲染。在浏览器执行绘制之前,useLayoutEffect内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

提示:

如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffectcomponentDidMountcomponentDidUpdate 的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect

如果你使用服务端渲染,请记住,无论useLayoutEffect还是 useEffect 都无法在 Javascript 代码加载完成之前执行。这就是为什么在服务端渲染组件中引入 useLayoutEffect 代码时会触发 React 告警。解决这个问题,需要将代码逻辑移至 useEffect 中(如果首次渲染不需要这段逻辑的情况下),或是将该组件延迟到客户端渲染完成后再显示(如果直到 useLayoutEffect 执行之前 HTML 都显示错乱的情况下)。

若要从服务端渲染的 HTML 中排除依赖布局 effect 的组件,可以通过使用 showChild && <Child /> 进行条件渲染,并使用 useEffect(() => { setShowChild(true); }, []) 延迟展示组件。这样,在客户端渲染完成之前,UI 就不会像之前那样显示错乱了。

看完官方解释表示还是一脸懵,连个Demo都不给,只能总结出如下两点

  1. useLayoutEffect需要在同步执行时使用,但是还是尽量别用,避免阻塞渲染
  2. 在服务端SSR渲染的时候,最好放弃useLayoutEffect

useEffect

默认情况下,都应该使用useEffectuseEffectcomponentDidMount、componentDidUpdate合并为同一个api

useEffect是异步的,所谓的异步就是利用requestIdleCallback,在浏览器空闲时间执行传入的callback。大部分情况下,用哪一个都是一样的,如果副作用执行比较长,比如大量计算,如果是useLayoutEffect就会造成渲染阻塞。

useLayoutEffect

这个是用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用到它,否则可能会出现闪屏的问题,useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制。

下面是大家都用的一个比较经典的例子:

import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
import { render } from 'react-dom';

function App() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    if (count === 0) {
      const randomNum = 10 + Math.random()*200
      setCount(10 + Math.random()*200);
    }
  }, [count]);

  return (
      <div onClick={() => setCount(0)}>{count}</div>
  );
}

render(<App />, document.getElementById('root'));

可以在线进行试验 stackblitz.com/edit/react-…

当点击div的时候,页面会更新一串随机数。当你连续点击时,你会发现这串数字在发生抖动。

原因在于,当你每次点击divcount会更新为0,之后useEffect内又把count改为一串随机数。

所以页面会先渲染成0,然后再渲染成随机数,由于更新很快,所以出现了闪烁

接下来我们将 useEffect 改为 useLayoutEffect:

import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
import { render } from 'react-dom';

function App() {
  const [count, setCount] = useState(0);
  
  useLayoutEffect(() => {
    if (count === 0) {
      const randomNum = 10 + Math.random()*200
      setCount(10 + Math.random()*200);
    }
  }, [count]);

  return (
      <div onClick={() => setCount(0)}>{count}</div>
  );
}

render(<App />, document.getElementById('root'));

可以在线进行试验 stackblitz.com/edit/react-…

相比使用 useEffect,当你点击 divcount 更新为 0,此时页面并不会渲染,而是等待 useLayoutEffect 内部状态修改后,才会去更新页面,所以页面不会闪烁。

总结:

  1. useLayoutEffect 相比 useEffect,通过同步执行状态更新可解决一些特性场景下的页面闪烁问题。
  2. useEffect 可以满足大部分的场景,而且 useLayoutEffect 会阻塞渲染,因此谨慎使用。

参考文档: uselayouteffect

When to useLayoutEffect Instead of useEffect

cloud.tencent.com/developer/a…