Part 07 · Effects · 7.1.03

Effects và đồng bộ external system

Effect không phải lifecycle callback dùng cho mọi side effect. Nó là cơ chế giữ một component React đồng bộ với network, browser API, DOM widget, subscription, timer hoặc hệ thống bên ngoài khác.

Mental model: render phải thuần; event handler xử lý tương tác cụ thể; Effect chỉ cần khi việc hiển thị component tự nó đòi hỏi thiết lập hoặc cập nhật một kết nối bên ngoài.

1. Khi nào cần và không cần Effect

Dùng Effect khi component phải synchronize với thứ React không sở hữu: mở kết nối chat, đăng ký event, điều khiển map widget, gửi analytics vì màn hình đã xuất hiện hoặc giữ media player theo prop. Nếu một giá trị tính được từ props/state hiện tại, hãy tính trong render. Nếu công việc xảy ra do click, submit hay purchase, đặt nó trong event handler vì handler giữ đúng nguyên nhân của hành động.

Nhu cầuVị trí phù hợpLý do
Lọc danh sách từ itemsqueryRender, có thể memoize nếu thực sự đắtDerived value, không có external system
Gửi request khi nhấn SaveEvent handlerPhải gắn với đúng tương tác của user
Subscribe theo roomIdEffectUI đang hiển thị phải đồng bộ với subscription
Reset toàn bộ subtree khi entity đổiĐổi key hoặc state ownershipIdentity diễn tả reset rõ hơn Effect
Thông báo parent sau một thao tácCùng event handlerTránh render con rồi Effect mới cập nhật ngược parent

2. Lifecycle: setup, cleanup và dependency change

Sau khi React commit UI, Effect chạy hàm setup. Khi dependency thay đổi, React chạy cleanup bằng giá trị cũ trước rồi mới setup bằng giá trị mới. Khi component unmount, cleanup chạy lần cuối. Hãy suy nghĩ theo từng process độc lập: kết nối thì disconnect, subscribe thì unsubscribe, timer thì clear, request thì abort hoặc bỏ qua kết quả stale.

useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();

  return () => connection.disconnect();
}, [serverUrl, roomId]);

Trong development, Strict Mode có thể chạy thêm một chu kỳ setup → cleanup → setup để phát hiện cleanup thiếu hoặc không đối xứng. Mục tiêu không phải ngăn lần chạy thứ hai bằng ref; hãy sửa resource lifecycle để user không nhận ra khác biệt giữa chạy một lần và chu kỳ kiểm tra đó.

3. Dependency là kết quả của code

Mọi reactive value được đọc trong Effect — props, state và biến/hàm khai báo trong component — phải có trong dependency list. Linter mô tả quan hệ dữ liệu thật của code; không nên “chọn” dependency bằng cách tắt rule. Nếu object hoặc function mới được tạo mỗi render làm Effect chạy lại, hãy đưa nó vào trong Effect, đưa constant ra ngoài component, hoặc tách Effect theo từng synchronization process.

Đừng chữa symptom bằng []. Empty dependencies chỉ đúng khi setup thực sự không đọc reactive value. Suppress linter thường tạo stale closure: callback tiếp tục thấy props/state của render cũ.

Khi cần đọc state mới nhất mà không muốn một đoạn logic làm Effect re-synchronize, dùng event handler nếu có thể. Với trường hợp Effect-specific, React mới cung cấp Effect Event để tách logic không-reactive; không dùng nó để che dependency đáng lẽ phải reactive.

4. Async race, fetch và cleanup đúng resource

Hai request có thể hoàn tất khác thứ tự khởi chạy. Cleanup cần abort request nếu API hỗ trợ và/hoặc đánh dấu kết quả cũ để nó không cập nhật state. Abort giảm công việc thừa; cờ ignore vẫn bảo vệ logic khi cancellation không đảm bảo. Cleanup phải remove đúng listener/function instance đã add và giải phóng đúng resource đã tạo trong setup.

useEffect(() => {
  const controller = new AbortController();
  let ignore = false;

  loadProfile(userId, { signal: controller.signal })
    .then(data => { if (!ignore) setProfile(data); })
    .catch(error => {
      if (error.name !== 'AbortError' && !ignore) setError(error);
    });

  return () => {
    ignore = true;
    controller.abort();
  };
}, [userId]);

Fetch trực tiếp trong Effect có thể tạo network waterfall, duplicate request, thiếu cache và không chạy hữu ích trong server render. Với ứng dụng thật, ưu tiên data API của framework/router hoặc server-state library để có preload, deduplication, cache, retry và request lifecycle rõ ràng.

5. Tách Effect theo synchronization process

Một Effect không nên gom các công việc chỉ vì chúng cùng cần chạy “sau render”. Nếu analytics phụ thuộc page còn connection phụ thuộc roomId, tách hai Effect để thay room không gửi lại analytics và thay page không reconnect. Ngược lại, setup và cleanup của cùng một resource phải ở chung một Effect để symmetry nhìn thấy được.

6. Kiểm thử và dấu hiệu lỗi

Test theo hành vi quan sát được: resource được kết nối với config đúng, cleanup khi dependency đổi/unmount, kết quả request cũ không thắng request mới, và Strict Mode không làm rò listener/timer. Fake timer hữu ích cho timer nhưng cần restore sau test; mock quá sâu implementation của Hook dễ làm test mong manh.

Review checklist: không có derived-state Effect; không suppress exhaustive-deps; cleanup đối xứng và idempotent; async có race policy; dependencies không bị nhiễu bởi identity được tạo vô ý; server rendering không phụ thuộc Effect để tạo HTML bắt buộc.
Tài liệu: Synchronizing with Effects · Lifecycle of Reactive Effects · Removing Effect Dependencies · You Might Not Need an Effect · useEffect API