useEffect
副作用、依赖数组、清理函数、fetch 数据。
useEffect
这一课你将能做到
- 说出 Effect 适合做什么、不适合做什么
- 写出带清理函数的
useEffect(避免组件卸了还在 setState) - 看懂依赖数组
[]/[id]的含义
为什么要学
有些事发生在「画完界面之后」:改 document.title、订阅窗口大小、按课程 slug 去拉进度。
这些叫与外部系统同步,用 useEffect。
人话:React 负责画界面;世界(浏览器、后端、定时器)不在 JSX 里,要另开一个「画完再做、走时再收尾」的通道。
常见误用:用 Effect 把 props 再抄一份到 state,结果多跑很多遍、输入光标乱跳。能在渲染时算出来的,不要 Effect。
先建立图像
组件渲染到屏幕
↓
执行 Effect
↓
依赖变了 → 先 cleanup(清理)→ 再跑新的 Effect
组件卸载 → cleanup
依赖数组是「什么变了才值得再同步一次」。
跟着做
步骤 1:改标题(最小外部同步)
import { useEffect, useState } from "react";
export function TitleDemo() {
const [name, setName] = useState("同学");
useEffect(() => {
document.title = "你好," + name;
}, [name]); // name 变了才改标题
return (
<input value={name} onChange={(e) => setName(e.target.value)} />
);
}
document.title 不是 React 树的一部分,所以用 Effect。
步骤 2:请求 + 清理(本课最重要)
用户从课 A 很快切到课 B:A 的响应可能后到,把 B 的标题盖掉。要用旗标或 AbortController。
import { useEffect, useState } from "react";
export function TrackTitle({ slug }) {
const [title, setTitle] = useState("");
useEffect(() => {
let cancelled = false;
async function load() {
const res = await fetch("/api/tracks/" + slug);
const data = await res.json();
if (!cancelled) setTitle(data.title);
}
load();
return () => {
cancelled = true; // 已换 slug 或卸载:别再 setState
};
}, [slug]);
return <h1>{title || "加载中…"}</h1>;
}
人话:请求回来时,人可能已经走了。
步骤 3:空依赖 = 挂载后跑(开发模式可能两次)
useEffect(() => {
console.log("挂载后同步一次");
return () => console.log("卸载前清理");
}, []);
React 严格模式在开发期会故意重复挂载,帮你发现漏清理。生产不会这样双重调用副作用。看到两次 log 先别慌。
步骤 4:订阅要对上取消订阅
useEffect(() => {
function onResize() {
// 读窗口宽度等
}
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);