👋 欢迎回到《前端达人 · React 播客书单》第 14 期(正文内容为学习笔记摘要,音频内容是详细的解读,方便你理解),请点击下方收听
视频版
今天我们聚焦一个让人又爱又怕的 Hook —— useEffect
,它是副作用的管理器,是函数组件的“隐藏执行者”。
我们将从基础语法,到执行机制,再到真实开发场景,完整拆解 useEffect
的使用逻辑与工作原理。
一、你为什么用不好 useEffect
?
很多人第一次用 useEffect
,就遇到这些问题:
🌀 “为什么副作用执行了两次?” 😱 “组件卸载时怎么还在请求接口?” 💣 “useEffect 写在按钮里怎么报错了?”
这些其实都跟它的规则和生命周期时机有关。
我们来带着这些问题,逐个拆解。
二、什么是副作用?(Side Effects)
📌 通俗解释:
副作用 = 不直接参与 UI 渲染、但“影响了组件生命周期”的逻辑行为
📦 常见副作用包括:
接口请求(fetch, axios)
添加 DOM 事件监听
设置定时器、interval
第三方库的注册 / 订阅
页面标题修改、手动操作 DOM
👀 可以类比成做饭时顺手擦桌子 —— 主任务是做饭,擦桌子是副任务,但它依然需要安排好时机执行。
三、基本语法与用法
useEffect
的标准写法如下:
useEffect(() => {
// 副作用代码
}, [依赖项]);
解释:
第一参数:副作用函数(Effect Function)
第二参数:依赖项数组(Dependencies Array)
📌 不同依赖写法 = 不同执行时机:
写法 | 执行时机说明 |
---|---|
无依赖项 | 每次渲染后都会执行 |
空数组 | 仅在挂载时执行一次(相当于 |
有依赖项 | 仅当指定依赖项发生变化时才重新执行副作用 |
代码示例:
// 每次渲染后都执行
useEffect(() => {
console.log("组件更新了");
});
// 指定依赖项
useEffect(() => {
console.log("search 变化了");
}, [search]);
// 只在挂载时执行一次
useEffect(() => {
console.log("组件加载了");
}, []);
✅ 推荐每次写 useEffect
都明确依赖数组,避免无意识触发。
四、注意!useEffect
的两条硬性规则
🧱 Hooks 规则必须记牢
:
✅ 只能在函数组件最顶层调用❌ 不能放在条件语句、循环、嵌套函数中
✅ 只能在函数组件或自定义 Hook 中调用❌ 不可在普通函数或 class 组件中使用
错误示例:
export function BadComponent() {
function handleClick() {
// ❌ 错误:在事件处理函数中调用 Hook
useEffect(() => {
console.log("点击副作用");
});
}
return <button onClick={handleClick}>Click</button>;
}
五、useEffect 的执行机制图解 🧠
生命周期时机 | useEffect 行为 |
---|---|
初次渲染完成 | 执行副作用函数 |
依赖项变化(非空) | 清除上一次副作用 → 执行新的副作用函数 |
组件卸载 | 清除副作用 |
📌 精髓:
有依赖项 → 比较依赖变化 → 触发副作用
副作用函数返回值是“清理函数” → 下一次执行前/卸载时运行
六、清理函数:打扫战场的副将
清理函数是
useEffect
返回的函数,它会在副作用重跑前或组件卸载时被调用
常见用途:
清除事件监听器
取消订阅
清除定时器
终止请求
代码示例:
useEffect(() => {
const handler = () => console.log("点击");
document.addEventListener("click", handler);
return () => {
document.removeEventListener("click", handler); // 清理
};
}, []);
🧼 类比生活:副作用是吃完饭,清理函数就是洗碗 ✨
七、实战:数据获取场景中的最佳实践
👨💻 获取后端数据,是 useEffect
最典型的用法之一。
🎯 场景:组件加载时请求数据并展示
export function PostsPage() {
const [posts, setPosts] = useState<PostData[]>([]);
useEffect(() => {
getPosts().then((data) => {
setPosts(data);
});
}, []);
}
📌 Tips:
如果使用 async/await,记得 不能直接在
useEffect
外层用 async!
useEffect(() => {
async function fetchData() {
const res = await fetch('/api/posts');
const data = await res.json();
setPosts(data);
}
fetchData(); // 内部调用 async 函数
}, []);
八、进阶优化:处理加载状态 & 错误处理
✅ 推荐增加 isLoading
和 error
状态来更好管理异步流程:
export function PostsPage() {
const [isLoading, setIsLoading] = useState(true);
const [posts, setPosts] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
async function fetchData() {
try {
const res = await fetch('/api/posts');
const data = await res.json();
if (!ignore) setPosts(data);
} catch (err) {
if (!ignore) setError(err);
} finally {
if (!ignore) setIsLoading(false);
}
}
fetchData();
// 清理副作用
return () => {
ignore = true;
};
}, []);
}
📌 说明:
使用
ignore
flag 防止组件卸载后仍更新状态真实项目中也可使用
AbortController
中断请求
九、React 严格模式下副作用执行两次?
⚠️ 在开发模式中,React 18 严格模式(StrictMode)下会故意模拟卸载 + 重建
你会看到副作用执行两次,这是“检测副作用是否安全”的设计
生产环境中只会执行一次,不用担心
🔟 总结回顾 🧠
✅ 关键词 | 📌 要点说明 |
---|---|
副作用(Side Effect) | 组件渲染之外的逻辑:获取数据、DOM 操作、订阅等 |
useEffect() | 处理副作用的 Hook,配合依赖项控制执行时机 |
清理函数 | 返回值函数:在下一次执行/卸载前清除副作用 |
异步请求最佳实践 | 使用 async/await + 错误处理 + loading 状态管理 |
Hook 使用规则 | 只能顶层调用,不能用于 class 组件、if/for 结构中 |
👋 喜欢这一期的朋友别忘了:
点赞、转发、收藏!有任何问题也欢迎在评论区留言,我会逐条解答!
我们下期见!🧑💻✨
#React #React播客 #前端达人 #前端播客 #CSS #TypeScript #TailwindCss #SVG