React 的 useEffect
清理函数详解
useEffect
是 React 中用于处理副作用(side effects)的 Hook,清理函数(Cleanup Function)是 useEffect
中返回的一个函数,用于清理或撤销副作用。清理函数的主要目的是确保副作用不会在组件卸载或依赖项更新时造成问题,比如内存泄漏或不必要的逻辑执行。
清理函数的基本语法
useEffect(() => {
// 副作用逻辑
console.log("Effect logic");
// 返回清理函数
return () => {
console.log("Cleanup logic");
};
}, [dependencies]); // 依赖项
工作机制
-
初始执行:
- 当组件首次渲染时,
useEffect
的副作用逻辑会执行。 - 此时,清理函数不会被调用。
- 当组件首次渲染时,
-
依赖项更新时:
- 如果依赖项(
dependencies
)发生变化,React 会先调用清理函数,然后再重新执行新的副作用逻辑。
- 如果依赖项(
-
组件卸载时:
- 当组件被卸载时,清理函数会被调用以撤销副作用。
清理函数的典型用途
清理函数的主要用途是清除或撤销在 useEffect
中创建的副作用,例如:
- 移除事件监听器。
- 取消网络请求或定时器。
- 清理订阅(如 WebSocket 或 Redux 订阅)。
- 释放内存资源。
举例说明
1. 清除事件监听器
如果在 useEffect
中添加了事件监听器,清理函数可以确保组件卸载时移除事件监听器。
javascript
import React, { useEffect } from "react";
function Example() {
useEffect(() => {
const handleResize = () => {
console.log("Window resized");
};
// 添加事件监听器
window.addEventListener("resize", handleResize);
// 清理函数:移除事件监听器
return () => {
window.removeEventListener("resize", handleResize);
};
}, []); // 空依赖数组,表示只在组件挂载和卸载时执行
return <div>Resize the window to see logs in the console</div>;
}
工作流程:
- 组件挂载时 :
- 添加
resize
事件监听器。
- 添加
- 组件卸载时 :
- 移除
resize
事件监听器,防止内存泄漏。
- 移除
2. 清除定时器
如果在 useEffect
中设置了定时器,清理函数可以确保组件卸载时清除定时器。
import React, { useState, useEffect } from "react";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
// 清理函数:清除定时器
return () => {
clearInterval(interval);
};
}, []); // 空依赖数组,表示只在组件挂载和卸载时执行
return <div>Count: {count}</div>;
}
工作流程:
- 组件挂载时 :
- 设置定时器,每秒更新
count
。
- 设置定时器,每秒更新
- 组件卸载时 :
- 清除定时器,防止组件卸载后继续更新状态。
3. 清理订阅
如果在 useEffect
中订阅了某些外部数据源(如 WebSocket 或 Redux),清理函数可以确保在组件卸载时取消订阅。
import React, { useEffect } from "react";
function WebSocketComponent() {
useEffect(() => {
const socket = new WebSocket("wss://example.com/socket");
socket.onmessage = (event) => {
console.log("Message from server:", event.data);
};
// 清理函数:关闭 WebSocket 连接
return () => {
socket.close();
};
}, []); // 空依赖数组,表示只在组件挂载和卸载时执行
return <div>WebSocket Component</div>;
}
工作流程:
- 组件挂载时 :
- 创建 WebSocket 连接。
- 监听消息。
- 组件卸载时 :
- 关闭 WebSocket 连接,释放资源。
4. 依赖项更新时的清理
如果 useEffect
的依赖项发生变化,清理函数会在旧的依赖被替换前执行。
import React, { useState, useEffect } from "react";
function DependencyExample() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Effect triggered for count: ${count}`);
return () => {
console.log(`Cleanup for count: ${count}`);
};
}, [count]); // 依赖 count
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
</div>
);
}
工作流程:
- 初始
count = 0
:useEffect
执行,输出Effect triggered for count: 0
。
count
更新为 1 :- 清理函数执行,输出
Cleanup for count: 0
。 - 然后,
useEffect
执行,输出Effect triggered for count: 1
。
- 清理函数执行,输出
清理函数的使用场景总结
场景 | 示例 | 清理函数作用 |
---|---|---|
事件监听器 | window.addEventListener |
移除事件监听器,防止内存泄漏 |
定时器 | setInterval 或 setTimeout |
清除定时器,防止组件卸载后继续执行 |
订阅 | WebSocket、Redux 订阅 | 取消订阅,释放资源 |
依赖项更新 | 动态依赖项 | 在依赖项变化时清理旧的副作用 |
外部库实例化或初始化 | 地图、图表库等初始化 | 销毁外部库实例,防止内存泄漏 |
useEffect
的清理函数运行时机
-
组件卸载时:
- 当组件被移除时,清理函数会被执行一次。
-
依赖项更新时:
- 如果
useEffect
的依赖项发生变化,React 会先执行清理函数,再执行新的副作用逻辑。
- 如果
-
React 渲染优化:
- 如果组件的依赖项没有变化,React 不会重新执行
useEffect
,也不会调用清理函数。
- 如果组件的依赖项没有变化,React 不会重新执行
最佳实践
-
始终清理副作用:
- 确保在
useEffect
中创建的任何副作用(如事件监听器、定时器、订阅)都在清理函数中正确清除。
- 确保在
-
避免过度依赖外部资源:
- 如果
useEffect
中使用了外部变量,确保它们被正确声明为依赖项,避免意外行为。
- 如果
-
理解依赖项行为:
- 确保依赖项的正确性,避免遗漏或过多依赖项,导致副作用重复执行。