如果你用过 Vue 3 的 Composition API(setup 语法),你会觉得这非常眼熟。如果你主要用 Vue 2(Options API),那你需要转换一下思维:React 没有明确的 mounted、updated、destroyed 钩子,所有的生命周期都在 useEffect 里解决。
1. 什么是 useEffect?(副作用)
在 React 函数组件里,主流程(函数体)必须是纯净的:输入 Props,返回 JSX。
任何“除此之外”的事情,都叫副作用 (Side Effects) ,比如:
- 修改
document.title - 发送 Ajax 请求 (API)
- 设置定时器 (
setInterval) - 手动操作 DOM
useEffect 就是 React 给你开的一个“后门”,让你在组件渲染完之后去做这些事。
2. Vue vs React:生命周期的映射
这是理解 useEffect 的一把钥匙。它的行为完全取决于第二个参数(依赖数组) 。
| Vue 生命周期 (Options API) | React useEffect 写法 | 含义 |
|---|---|---|
mounted (只执行一次) | useEffect(() => { ... }, []) | 数组是空的,代表“我不依赖任何变量,所以我只在出生时运行一次”。 |
watch / updated (数据变了就执行) | useEffect(() => { ... }, [count]) | 数组里有 count,代表“只要 count 变了,我就运行”。 |
updated (任意更新都执行) | useEffect(() => { ... }) | 没写数组。每次组件渲染它都跑。(⚠️ 慎用,容易死循环) |
beforeUnmount (销毁前) | return () => { ... } | 在 useEffect 的函数里返回一个函数,这个返回的函数会在销毁时执行。 |
3. 实战演示:标题随计数器变化
需求: 每次点击按钮更新 count 时,浏览器的标签页标题 (document.title) 也要跟着变成 "当前次数: X"。
代码对比:
Vue 写法 (Watch)
JavaScript
// Vue 3
watch(count, (newVal) => {
document.title = `当前次数: ${newVal}`;
});
React 写法 (useEffect)
JavaScript
import { useState, useEffect } from 'react'; // 1. 引入
function App() {
const [count, setCount] = useState(0);
// 2. 使用 useEffect
// 翻译:当 [count] 发生变化之后,执行这个箭头函数
useEffect(() => {
document.title = `当前次数: ${count}`;
console.log("副作用执行了!");
}, [count]); // <--- 关键在这里!依赖数组
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
实验:
如果你把 [count] 改成空数组 [],你会发现:只有刷新页面第一次标题变了,后面怎么点按钮,标题都不动。因为 React 认为“这个副作用不需要依赖任何状态更新”。
4. 难点:清理副作用 (Cleanup)
这是 Vue 开发者最容易懵的地方。
在 Vue 里,你在 mounted 里开启定时器,必须记得在 beforeUnmount 里清除。
在 React 里,这写在同一个地方。
场景: 做一个自动计时器。
JavaScript
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 1. 开启定时器 (相当于 mounted)
const intervalId = setInterval(() => {
console.log("定时器在跑...");
setSeconds(s => s + 1);
}, 1000);
// 2. 返回清理函数 (相当于 beforeUnmount)
// React 会在组件销毁时,或者下一次 effect 执行前,调用这个函数
return () => {
console.log("清除定时器!");
clearInterval(intervalId);
};
}, []); // [] 代表只在挂载时启动一次
return <h1>已经过去了 {seconds} 秒</h1>;
}
逻辑闭环: React 认为“如何创建”和“如何销毁”是强相关的逻辑,所以强制你写在一起,防止你忘记清除。
💡 核心思维转换总结
- Vue: 是基于时机的 (这时候该干嘛 -> mounted, destroyed)。
- React: 是基于依赖的 (数据变了该干嘛 -> 依赖数组)。
你只要问自己两个问题:
-
我想做什么? (写在函数体里)
-
我想什么时候做?
- 只有出生时? ->
[] userId变的时候? ->[userId]- 只要有风吹草动? -> 不传数组 (危险)
- 只有出生时? ->