useEffect 是 React 中另一个核心 Hook,主要用于处理副作用(Side Effects),可以把它理解为函数组件中替代类组件生命周期方法(如 componentDidMount、componentDidUpdate、componentWillUnmount)的统一方案。
一、核心作用
- 处理组件的副作用操作:比如数据请求、DOM 操作、订阅事件、定时器、清理定时器/订阅等。
- 监听状态/属性变化,触发对应逻辑:当指定依赖项改变时,自动执行副作用函数。
- 实现组件的生命周期逻辑:统一管理组件挂载、更新、卸载时的操作,避免分散在多个生命周期方法中。
二、基本用法
1. 引入与语法
从 react 中导入 useEffect,在函数组件顶层调用,语法如下:
import { useEffect } from 'react';
function 组件名() {
// 语法:useEffect(副作用函数, 依赖数组);
useEffect(() => {
// 执行副作用操作(如请求数据、操作DOM)
// 可选:返回清理函数,组件卸载或依赖变化时执行
return () => {
// 清理操作(如清除定时器、取消订阅)
};
}, [依赖项1, 依赖项2]); // 依赖数组:指定触发副作用的变量
return UI结构;
}
2. 关键参数说明
- 副作用函数:要执行的逻辑代码,比如发送网络请求、修改 DOM、设置定时器等。
- 依赖数组(可选):控制副作用函数的执行时机,是
useEffect的核心:- 不传依赖数组:组件**每次渲染(挂载+更新)**后都会执行副作用函数。
- 空数组
[]:仅在组件首次挂载时执行一次,类似componentDidMount。 - 包含依赖项:仅当依赖项中的变量发生变化时,才会执行副作用函数,类似
componentDidUpdate。
- 清理函数(可选):副作用函数返回的函数,用于清理副作用(如清除定时器、取消网络请求),在组件卸载前或下一次副作用执行前触发,避免内存泄漏。
三、实用示例
示例 1:基础数据请求(挂载时执行一次)
最常见的场景是组件挂载后请求后端数据,空依赖数组确保只执行一次:
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
// 空依赖数组:仅组件挂载时执行一次
useEffect(() => {
// 异步请求数据
const fetchUsers = async () => {
try {
const res = await fetch('https://api.example.com/users');
const data = await res.json();
setUsers(data);
} catch (err) {
console.error('请求失败:', err);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []); // 空依赖,只执行一次
if (loading) return <div>加载中...</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
示例 2:监听状态变化(带依赖项)
当指定状态变化时,自动触发副作用逻辑,比如根据搜索关键词请求数据:
import { useState, useEffect } from 'react';
function Search() {
const [keyword, setKeyword] = useState('');
const [result, setResult] = useState([]);
// 依赖项包含 keyword:仅 keyword 变化时执行
useEffect(() => {
// 模拟根据关键词搜索
const searchData = async () => {
if (!keyword) {
setResult([]);
return;
}
// 模拟请求延迟
const timer = setTimeout(() => {
setResult([`结果1:${keyword}`, `结果2:${keyword}`]);
}, 500);
// 清理:关键词变化时清除上一次定时器,避免重复请求
return () => clearTimeout(timer);
};
searchData();
}, [keyword]); // 依赖 keyword,变化则执行
return (
<div>
<input
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="输入搜索关键词"
/>
<ul>
{result.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
示例 3:定时器与清理(避免内存泄漏)
组件挂载时设置定时器,卸载时必须清理,否则会造成内存泄漏:
import { useState, useEffect } from 'react';
function Timer() {
const [time, setTime] = useState(0);
useEffect(() => {
// 挂载时设置定时器
const timer = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
// 清理函数:组件卸载时清除定时器
return () => clearInterval(timer);
}, []); // 空依赖,只设置一次定时器
return <div>当前计时:{time} 秒</div>;
}
示例 4:DOM 操作(修改页面标题)
直接操作 DOM 属于副作用,通过 useEffect 实现,监听状态变化同步更新标题:
import { useState, useEffect } from 'react';
function PageTitle() {
const [count, setCount] = useState(0);
// 每次 count 变化,更新页面标题
useEffect(() => {
document.title = `计数:${count}`;
}, [count]); // 依赖 count,变化则更新标题
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
四、核心特性
- 统一生命周期:一个 Hook 替代
componentDidMount、componentDidUpdate、componentWillUnmount,逻辑更集中。 - 依赖驱动执行:通过依赖数组精准控制副作用的触发时机,避免不必要的重复执行。
- 自动清理机制:返回清理函数,无需手动管理卸载逻辑,有效防止内存泄漏。
- 可多次使用:一个组件中可以使用多个
useEffect,分离不同的副作用逻辑(如一个处理请求,一个处理定时器)。 - 执行时机:默认在浏览器绘制完成后异步执行,不会阻塞页面渲染;若需同步执行(如测量 DOM),可使用
useLayoutEffect。
五、常见误区
- 遗漏依赖项:副作用函数中使用了外部变量,但未加入依赖数组,会导致逻辑错误(React 会给出警告)。
- 无限循环执行:依赖数组包含引用类型(如对象、数组),每次渲染引用变化,导致
useEffect无限执行,需用useMemo/useCallback优化。 - 忘记清理副作用:未清除定时器、订阅、未完成的请求,会造成内存泄漏或异常行为。
- 在副作用中直接修改状态:若未正确设置依赖,可能导致状态更新触发无限循环。
- 依赖数组传空却使用外部变量:空依赖数组表示不监听任何变量,若函数内使用了外部状态/属性,需加入依赖。
总结
useEffect是处理函数组件副作用的核心 Hook,统一管理挂载、更新、卸载逻辑。- 依赖数组是控制执行时机的关键:空数组仅挂载执行,传依赖项则监听变化,不传则每次渲染都执行。
- 必须用清理函数处理定时器、订阅等副作用,避免内存泄漏,同时遵循依赖规则,保证逻辑正确性。
