REACT CORE · 99
Effects / Data Fetching / Cleanup:Effect 是同步外部系統,不是萬用初始化區
如果一個值能由 props/state 直接計算,就先在 render 算。Effect 主要用來同步 React 外部世界,例如 network、timer、DOM API、subscription。
Learning outcomes
- 能判斷何時不需要 effect。
- 能解釋 dependency array 代表哪些 reactive values 會影響同步。
- 能實作 cleanup / cancellation。
- 能避免 stale request 覆蓋新 state。
1. Derived data 不需要另一份 state
const passed =
courses.filter(
course =>
course.score !== null
&& course.score >= 60
);
不要:
const [
passed,
setPassed
] = useState([]);
useEffect(() => {
setPassed(
courses.filter(...)
);
}, [courses]);
後者多了一份必須同步的 state,增加 inconsistency 風險。
2. Effect:同步 React 外部系統
useEffect(() => {
document.title =
"Courses: "
+ courses.length;
}, [courses.length]);
document.title 是 React state 以外的 Browser system,因此 effect 合理。
3. Fetch + loading/error state
useEffect(() => {
const controller =
new AbortController();
async function load() {
setStatus("loading");
try {
const response =
await fetch(
"/api/courses",
{
signal:
controller.signal
}
);
if (!response.ok) {
throw new Error(
"HTTP "
+ response.status
);
}
const data =
await response.json();
setCourses(data);
setStatus("success");
} catch (error) {
if (
error.name
=== "AbortError"
) {
return;
}
setStatus("error");
}
}
load();
return () => {
controller.abort();
};
}, []);
4. Cleanup 的目的不是儀式
component unmount、dependency 改變前,上一個 effect 需要停止 subscription / timer / request,否則舊工作可能繼續影響已經不再需要的 UI。
5. Stale response race
使用者快速切換 course id:
id = 1 → request A
id = 2 → request B
B 先回來
A 後回來
若不取消/guard A,舊資料可能覆蓋新畫面。這和 132 Advanced Async 是同一個核心問題,只是現在發生在 React lifecycle。
6. Dependencies:不是「想跑幾次就怎麼填」
Dependency array 應反映 effect 內使用的 reactive values,而不是拿來手動控制 lifecycle。若 effect 依賴某個 prop/state,就應思考它變更時是否需要重新同步。
Project checkpoint:Course Dashboard v4
mountfetch /api/coursesloadingvalidate/parsesetCoursesrender
到這裡 Course Dashboard 已經從 local-only app 變成 client UI。
Debug evidence:infinite effect loop
useEffect(() => {
setCourses(
[...courses]
);
}, [courses]);
effect 改 courses → courses change → effect 再跑。先畫 dependency / state transition loop,不要只亂刪 dependency。
Knowledge check
- 為什麼 derived data 通常不需要 useEffect?
- cleanup 解決哪種 lifecycle / stale work 問題?
- 設計一個 id 改變時會取消舊 fetch 的 effect。
- 看到 effect 無限重跑時,如何畫出 state transition loop?