ReactHook - useEffect、useLayoutEffect
useEffect
作用:
-
推迟代码执行
渲染完成之后再执行,常用于 数据读取 -
完成移除 - 组件卸载的时候做一些清理的工作
例如:移除定时器、事件监听器等
有关组件卸载:
触发时机:当组件从 DOM 中移除时,会触发useEffect里返回的函数,useEffect里的内容不会执行,执行也没有意义了。当组件出现在DOM中时,会执行useEffect里的代码,useEffect里返回的函数不执行。
import React, { useState, useEffect } from 'react';
function TimerComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 返回的函数将在组件卸载时执行
return () => {
clearInterval(timer);
console.log('Timer cleared');
};
}, []); // 空依赖数组,effect 只在挂载和卸载时运行
return <div>Timer: {count}</div>;
}
export default TimerComponent;
该行为是模拟了componentWillUnmount,常用于清理操作,例如取消网络请求、清除计时器或移除事件监听器。
补充:何时执行componentWillUnmount?
-
组件从页面移除:当用户导航到另一个页面或条件渲染使得组件不再显示时,componentWillUnmount 会被调用。在上面的例子中,当 TimerComponent 被移除时,计时器会被清除。
-
父组件卸载:如果父组件卸载了,所有子组件也会卸载,它们的 componentWillUnmount 也会被调用。
- 第二个参数来模拟生命周期。
对于缓存的对象来说,因为每次函数都重新执行,因此对象的引用会变,所以useEffect第二个参数缓存的是引用的地址。
useEffect存在一种bail out(保释) / (补救)机制:
当你尝试设置相同的状态,仅仅让你把函数执行完,但不会再触发effect。
同理,在class组件中的setState也是一样。
setState
state设置重复的值 —— 只会重复渲染一次 +1
1. 推迟代码执行
例如:
上面其实本来就是一种错误的写法,只是React官方帮你兜了个底。
正确的做法是:
2. 完成移除 定时器、监听
function HOC() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('创建')
return () => {
console.log('销毁')
}
});
return(
<>
<button onClick={() => setCount(count + 1)}>按钮</button>
</>
)
}
export default HOC;
当状态变化的时候,重新渲染,重新渲染导致useEffect执行(前提是没有依赖项)。在第一次刷新的时候,不执行useEffect返回的函数,只执行useEffect里的内容。后面状态变化导致重新渲染的时候,会优先执行useEffect返回的函数,然后再执行useEffect里的内容。
例如:
页面初始化时:
第一次点击按钮时:
useEffect - clean-up阶段有两个:
- 删除组件
- 重新渲染当前组件
1. 第一个,删除组件的时候执行:

- 注意:
- useEffect里的回调如果有返回值的话,必须是一个函数。
- 会先执行这个return函数,例如下面的代码。
2. 第二个,重新渲染的时候执行:

上面两种情景这样做的官方总结:
react官方:
1.必要性:极大的提升程序的可靠性
2.函数组件:简单、小型组件
程序可靠性
程序性能
结论:重新渲染的时候就先移除然后重新添加,目的是为了增强稳定性,如果有很庞大的初始化、移除操作,还应该用class(didMount、willUnmount)
注意:
错误的使用state,会导致资源泄露
例如:
1.某种异步操作开始——非常长
2.组件已经被卸载了
3.setState - 资源(内存泄露、socket泄露,主要是内存泄露)泄露
代码如下:
会报如下警告⚠️:
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in Unknown (at App.js:13)
解决方法:
3. 第三种, 第二个参数来模拟生命周期。
useEffect选择执行:
当a变化,才打印effect a;
当b变化,才打印effect b;
类似于 [a] 这种形式 官方叫做 ”跳过“
useEffect模拟生存周期函数:
useEffect(fn, [值]);
如:componentDidMount
useEffect() 的callback返回函数的case,其实是根据当前操作来的,是添加移除定时器还是添加移除dom,执行效果是不一样的,具体可以参考useEffect() 有始有终
useLayoutEffect 是 React 提供的一个 Hook,你可以把它理解为**“在浏览器把画面绘制出来之前,React 给你的最后一次修改 DOM 的机会”**。
它的功能签名和 useEffect 几乎一模一样,但核心区别在于执行时机。
useEffect vs useLayoutEffect
为了理解它,我们需要看浏览器的渲染流程:React 更新 DOM → useLayoutEffect 执行 → 浏览器绘制 (Paint) → useEffect 执行
| 特性 | useEffect (常用) | useLayoutEffect (特殊) |
|---|---|---|
| 执行时机 | 浏览器绘制完成后异步执行 | 浏览器绘制之前同步执行 |
| 视觉体验 | 如果修改了布局,用户可能会看到闪烁(先显示旧位置,再跳到新位置) | 用户看不到中间状态,直接看到最终结果,无闪烁 |
| 性能影响 | 不阻塞浏览器绘制,性能较好 | 会阻塞浏览器绘制,用多了会导致页面卡顿 |
| 比喻 | 演出开始后,你在后台改道具 | 幕布拉开前,你冲上去把道具摆好 |
💡 什么时候必须用 useLayoutEffect?
只有当你需要读取 DOM 布局(如宽高、位置)并立即同步修改 DOM 时,才使用它。主要用于解决“视觉闪烁”问题。
典型场景 1:防止弹窗/气泡定位闪烁
假设你要做一个 Tooltip(气泡提示),它需要显示在按钮的上方。
- 如果用
useEffect:页面先渲染 Tooltip 在默认位置(比如左上角) -> 浏览器绘制 ->useEffect计算按钮位置 -> 修改 Tooltip 位置 -> 用户看到 Tooltip “闪”了一下跳到了按钮上方。 - 如果用
useLayoutEffect:React 更新 DOM ->useLayoutEffect立即计算位置并修改 -> 浏览器绘制(直接画出正确位置的 Tooltip) -> 用户完全感觉不到位置修正的过程。
典型场景 2:根据内容调整布局
比如你需要根据一个容器的实际宽度,来决定里面放几个卡片。你需要先测量容器宽度,再决定渲染逻辑,这个过程必须在绘制前完成。
典型场景 3:强制同步滚动
比如进入聊天室,你希望滚动条直接到底部,不想让用户看到“先在顶部再跳到底部”的过程。
⚠️ 什么时候不要用?
绝大多数情况下,请优先使用 useEffect。
以下场景不要用 useLayoutEffect:
- 数据请求 (API 调用)
- 事件订阅/监听
- 日志记录、埋点
- 写入 LocalStorage
- 任何不依赖 DOM 测量的操作
因为在这些场景下使用 useLayoutEffect 只会徒增性能开销,阻塞页面渲染,带来卡顿风险。
📌 总结口诀
“涉及测量和布局修正,怕闪就用
useLayoutEffect;其他所有副作用,统统用useEffect。”
useLayoutEffect 是 React 16.8.0 版本引入的。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)