Skip to content

useLayoutEffect

用于在浏览器重新绘制屏幕之前触发。与useEffect类似。

语法

js
useLayoutEffect(() => {
  // 副作用代码
  return () => {
    // 清理代码
  }
}, [dependencies]);
  • setupEffect处理函数,可以返回一个清理函数。组件挂载时执行setup,依赖项更新时先执行cleanup再执行setup,组件卸载时执行cleanup
  • dependencies(可选):setup中使用到的响应式值列表(propsstate等)。必须以数组形式编写如[dep1, dep2]。不传则每次重渲染都执行Effect

useLayoutEffect返回 undefined

区别(useLayoutEffect/useEffect)

区别useLayoutEffectuseEffect
执行时机浏览器完成布局和绘制之前执行副作用浏览器完成布局和绘制之后执行副作用
执行方式同步执行异步执行
DOM渲染阻塞DOM渲染不阻塞DOM渲染

测试DOM阻塞

下面这个例子展示了useLayoutEffectuseEffect在DOM渲染时的区别。useLayoutEffect会阻塞DOM渲染,而useEffect不会。

tsx
import React, { useLayoutEffect, useEffect, useState } from 'react';

function App() {
   const [count, setCount] = useState(0)
   //不阻塞DOM
   // useEffect(() => {
   //    for (let i = 0; i < 50000; i++) {
   //       //console.log(i);
   //       setCount(count => count + 1)
   //    }
   // }, []);
   //阻塞DOM
   // useLayoutEffect(() => {
   //    for (let i = 0; i < 50000; i++) {
   //       //console.log(i);
   //       setCount(count => count + 1)
   //    }
   // }, []);
   return (
      <div>
         <div>app </div>
         {
            Array.from({ length: count }).map((_, index) => (
               <div key={index}>{index}</div>
            ))
         }
      </div>
   );
}

export default App;

测试同步异步渲染

在下面的动画示例代码中:

  1. useEffect 实现的动画效果:
    • 初始渲染时 opacity: 0
    • 浏览器完成绘制
    • useEffect 异步执行,设置 opacity: 1
    • 用户可以看到完整的淡入动画过渡效果
  2. useLayoutEffect 实现的动画效果:
    • 初始渲染时 opacity: 0
    • DOM 更新后立即同步执行 useLayoutEffect
    • 设置 opacity: 1
    • 浏览器绘制时已经是最终状态
    • 用户看不到过渡动画效果
css
#app1 {
    width: 200px;
    height: 200px;
    background: red;
}

#app2 {
    width: 200px;
    height: 200px;
    background: blue;
    margin-top: 20px;
    position: absolute;
    top: 230px;
}
tsx
import React, { useLayoutEffect, useEffect, useRef } from 'react';

function App() {


   // 使用 useEffect 实现动画效果
   useEffect(() => {
      const app1 = document.getElementById('app1') as HTMLDivElement;
      app1.style.transition = 'opacity 3s';
      app1.style.opacity = '1';
   }, []);

   // 使用 useLayoutEffect 实现动画效果
   useLayoutEffect(() => {
      const app2 = document.getElementById('app2') as HTMLDivElement;
      app2.style.transition = 'opacity 3s';
      app2.style.opacity = '1';

   }, []);

   return (
      <div>
         <div id="app1"  style={{ opacity: 0 }}>app1</div>
         <div id="app2"  style={{ opacity: 0 }}>app2</div>
      </div>
   );
}

export default App;

应用场景

  • 需要同步读取或更改DOM:例如,你需要读取元素的大小或位置并在渲染前进行调整。
  • 防止闪烁:在某些情况下,异步的useEffect可能会导致可见的布局跳动或闪烁。例如,动画的启动或某些可见的快速DOM更改。
  • 模拟生命周期方法:如果你正在将旧的类组件迁移到功能组件,并需要模拟componentDidMountcomponentDidUpdatecomponentWillUnmount的同步行为。

用法

记录滚动位置,等用户访问这个网页时,滚动到之前记录的位置。增强用户体验

tsx
import { useLayoutEffect } from 'react'

function App() {
  const scrollHandle = (e: React.UIEvent<HTMLDivElement>) => {
    const scrollTop = e.currentTarget.scrollTop
    console.log(scrollTop)
    window.history.replaceState({}, '', `?scrollTop=${scrollTop}`)
  }

  useLayoutEffect(() => {
    const container = document.getElementById('container') as HTMLDivElement
    const top = window.location.search.split('=')[1]
    container.scrollTop = Number(top)
    console.log(container.scrollTop)
  })

  return (
    <div
      onScroll={scrollHandle}
      id="container"
      style={{ height: 500, overflow: 'auto' }}
    >
      {Array.from({ length: 500 }).map((_, index) => (
        <div key={index}>{index}</div>
      ))}
    </div>
  )
}

export default App

滚动条的位置也可以记录在localStorage,这里记录在地址栏中