最近在复习React hook相关用法知识,尝试优化当前项目上关于性能与展示优化的一些问题,需要重新回顾一下useEffect
与useLayoutEffect
的用法以及原理
什么是Effect
Effect
被翻译为 副作用
, React
中的Effect
是一种用于处理副作用操作的机制,指的是操作那些与组件渲染无关的任务,例如:数据获取、订阅事件、手动操作DOM等。Effect
能够在组件被挂载、更新或者卸载时执行、其目的是将这些操作与组件的逻辑分离,确保它们在正确的时间点被执行。
常见用途
- 数据获取
- 事件订阅
- 清理资源
- 状态变化监听
- ...
useEffect
基础用法
javascript
useEffect(setup, dependencies?)
javascript
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
以官网的demo代码为例,setup为一个函数,里面的运行逻辑依赖第二个参数中的依赖列表[serverUrl, roomId] 其中依赖数据的变化会导致setup函数的运行,默认组件挂载时候也会执行一次。 依赖参数的填写需要注意,不写任何数据、空数组、有数据 的情况均反馈不同,不写任何依赖的情况下,每次触发组件渲染均会执行setup函数,而写空数组则之后在组件挂载时触发一次,而写了关系的依赖则会根据依赖数据的状态变化而判断是否需要执行。 注意:依赖参数慎用空值
setup函数可以返回一个cleanup函数,其执行点会在每次依赖项变化重新渲染后,React会将旧值运行cleanup函数,然后再使用新值执行setup函数,另外在组件销毁时,也会执行一次cleanup函数。 一般cleanup函数里面可以做一些timer、dom eventListener等的销毁解绑等操作。
useLayoutEffect
基础用法
与useEffect
api 用法一致。
javascript
useLayoutEffect(setup, dependencies?)
两者差异
既然两者用法一样,为何会提供两个不一样的hook工具呢?
text
- If your Effect wasn't caused by an interaction (like a click), React will generally let the browser **paint the updated screen first before running your Effect.** If your Effect is doing something visual (for example, positioning a tooltip), and the delay is noticeable (for example, it flickers), replace `useEffect` with [`useLayoutEffect`.](https://zh-hant.react.dev/reference/react/useLayoutEffect)
- If your Effect is caused by an interaction (like a click), **React may run your Effect before the browser paints the updated screen**. This ensures that the result of the Effect can be observed by the event system. Usually, this works as expected. However, if you must defer the work until after paint, such as an `alert()`, you can use `setTimeout`. See [reactwg/react-18/128](https://github.com/reactwg/react-18/discussions/128) for more information.
- Even if your Effect was caused by an interaction (like a click), **React may allow the browser to repaint the screen before processing the state updates inside your Effect.** Usually, this works as expected. However, if you must block the browser from repainting the screen, you need to replace `useEffect` with [`useLayoutEffect`.](https://zh-hant.react.dev/reference/react/useLayoutEffect)
The code inside useLayoutEffect and all state updates scheduled from it block the browser from repainting the screen. When used excessively, this makes your app slow. When possible, prefer useEffect.
从官网上查到此内容
其大概的意思是说 如果你的effect不是由交互(比如点击)引起的, React通常会让浏览器在运行你的effect之前先绘制更新的屏幕。如果你的effect是在做一些视觉上的事情(例如,定位工具提示),并且延迟是明显的(例如,它闪烁),用uselayouteeffect替换useEffect 如果您的Effect是由交互(如点击)引起的,React可能会在浏览器绘制更新的屏幕之前运行您的Effect。这确保了事件系统可以观察到Effect的结果。通常情况下,这是预期的。但是,如果您必须将工作延迟到绘制之后,例如alert(),则可以使用setTimeout。
即使你的Effect是由交互(比如点击)引起的,React也可能允许浏览器在处理你的Effect中的状态更新之前重新绘制屏幕。通常情况下,这是预期的。但是,如果您必须阻止浏览器重新绘制屏幕,则需要将useEffect替换为uselayouteeffect。
useLayoutEffect中的代码和从它调度的所有状态更新阻止浏览器重新绘制屏幕。当过度使用时,这会使你的应用变慢。如果可能的话,使用useEffect。
这表达的有点很模糊,总结一下
执行时机不同
useEffect
在组件渲染到屏幕之后异步执行setup, 这意味着它不会阻塞浏览器的绘制和更新、适用于大多数与数据获取、订阅事件、手动修改DOM 等不会直接影响页面布局和视觉呈现的操作useLayoutEffect
会在浏览器进行布局和绘制之前同步执行。useLayoutEffect中执行操作会修改DOM样式和结构,并且在浏览器绘制之前就完成这些修改,避免页面的重绘和回流带来的性能问题。
渲染时机影响不同
useEffect
的执行不会阻塞浏览器渲染工作useLayoutEffect
的执行可能会阻塞浏览器的渲染,需要注意性能问题,不要运行耗时过长的任务。
useEffect | useLayoutEffect | |
---|---|---|
触发时机 | 在浏览器绘制完成后 异步执行 | 在DOM更新后、浏览器绘制前同步执行 |
阻塞渲染 | 不会阻塞浏览器绘制 | 阻塞浏览器绘制直到回调完成 |
底层原理对比
React 渲染流程大致分为以下4点
- Render Phase (生成虚拟DOM)
- Commit Phase (更新真实DOM)
- Browser Paint (浏览器绘制页面)
- Effect Callbacks (执行UseEffect)
useLayoutEffect
的回调在Commit Phase
后立即执行(步骤2/3之间)useEffect
的回调在Browser Paint
后执行 (步骤3之后)
使用场景对比
适合useLayoutEffect
的场景
- 需要同步修改DOM 例如调整元素尺寸位置
- 需要在浏览器回之前消除视觉闪烁(如快速切换状态导致的布局抖动)
- 需要读取DOM布局信息 如滚动位置、元素尺寸
适合useEffect
的场景
- 数据获取
- 事件监听
- 日志记录
- 大多数操作