一、核心概念:什么是 useEffect?
首先,先理解两个关键概念:
首先,先理解两个关键概念:
副作用(Side Effect):指组件渲染完成后需要执行的操作,比如:
数据请求(接口调用)
DOM 操作(修改 DOM 样式、添加事件监听)
订阅 / 取消订阅(定时器、WebSocket 连接)
手动修改 state(非通过 setState)
useEffect 作用:替代类组件中的 componentDidMount、componentDidUpdate、componentWillUnmount 三个生命周期方法,统一处理副作用逻辑。
二、基本语法
import { useEffect } from 'react';
function MyComponent() {
// 基础用法
useEffect(() => {
// 副作用逻辑(渲染后执行)
// 可选的清理函数(组件卸载/下一次effect执行前执行)
return () => {
// 清理操作(比如清除定时器、取消订阅)
};
}, [依赖项数组]); // 依赖项:控制effect何时重新执行
}
三、不同用法场景(核心重点)
场景 1:无依赖项数组 → 每次渲染后都执行
相当于类组件的 componentDidMount + componentDidUpdate。
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 每次组件渲染(count变化)后执行
useEffect(() => {
console.log(`当前count: ${count}`); // 渲染后打印count
document.title = `点击了${count}次`; // 修改DOM(副作用)
}); // 无依赖项数组
return (
<button onClick={() => setCount(count + 1)}>
点击次数:{count}
</button>
);
}
执行时机:组件首次渲染后执行,每次状态更新(count 变化)重新渲染后也执行。
场景 2:依赖项为空数组 → 仅首次渲染执行
相当于类组件的 componentDidMount,适合只需要执行一次的操作(比如初始化请求、添加全局监听)。
import { useEffect } from 'react';
function UserList() {
useEffect(() => {
// 仅首次渲染时请求数据(只执行一次)
const fetchUsers = async () => {
const res = await fetch('https://api.example.com/users');
const data = await res.json();
console.log('用户数据:', data);
};
fetchUsers();
// 清理函数:组件卸载时执行(比如取消请求)
return () => {
console.log('组件卸载,清理资源');
};
}, []); // 空依赖数组
return <div>用户列表</div>;
}
执行时机:仅组件首次渲染完成后执行一次,清理函数在组件卸载时执行。
场景 3:依赖项数组包含特定值 → 仅依赖项变化时执行
相当于类组件的 componentDidUpdate(仅监听指定状态),是最常用的场景。
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 仅当userId变化时,重新请求用户信息
useEffect(() => {
const fetchUser = async () => {
const res = await fetch(`https://api.example.com/users/${userId}`);
const data = await res.json();
setUser(data);
};
fetchUser();
// 清理函数:下一次effect执行前/组件卸载时执行
return () => {
console.log(`停止请求userId: ${userId}的信息`);
};
}, [userId]); // 依赖项:userId
if (!user) return <div>加载中...</div>;
return <div>用户名:{user.name}</div>;
}
执行时机:首次渲染执行 + 每次 userId 变化时重新执行,清理函数会在 “下一次 effect 执行前” 先执行。
四、关键注意事项
1.清理函数的必要性:
比如定时器如果不清理,组件卸载后仍会执行,导致内存泄漏:
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
// 清理函数:清除定时器
return () => clearInterval(timer);
}, []);
2.依赖项的正确性:
useEffect 内部用到的变量(如 state、props、函数)都必须加入依赖项数组,否则会捕获旧值(闭包问题)。
可以用 ESLint 规则 react-hooks/exhaustive-deps 自动检查依赖项是否完整。
3.effect 是同步的:
不要在 useEffect 外层加 async/await(会导致返回值变成 Promise,而非清理函数),正确写法是在内部定义异步函数:
// 错误写法
// useEffect(async () => { ... }, []);
// 正确写法
useEffect(() => {
const asyncFn = async () => { ... };
asyncFn();
}, []);
总结
- useEffect 是 React 函数组件处理副作用的核心 Hook,替代类组件的三个生命周期方法。
- 依赖项数组决定执行时机:
- 无数组 → 每次渲染执行;
- 空数组 → 仅首次渲染执行;
- 有值数组 → 依赖项变化时执行。
- 清理函数用于释放资源(定时器、订阅、请求),避免内存泄漏,执行时机是 “组件卸载前” 或 “下一次 effect 执行前”