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.
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ầu | Vị trí phù hợp | Lý do |
|---|---|---|
Lọc danh sách từ items và query | Render, có thể memoize nếu thực sự đắt | Derived value, không có external system |
| Gửi request khi nhấn Save | Event handler | Phải gắn với đúng tương tác của user |
Subscribe theo roomId | Effect | UI đang hiển thị phải đồng bộ với subscription |
| Reset toàn bộ subtree khi entity đổi | Đổi key hoặc state ownership | Identity diễn tả reset rõ hơn Effect |
| Thông báo parent sau một thao tác | Cùng event handler | Trá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.
[]. 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.
- Mỗi Effect trả lời: external system nào đang được đồng bộ?
- Dependency nào thực sự thay đổi configuration của process đó?
- Cleanup có hoàn tác chính xác setup trước đó không?
- Logic có thể chuyển về render, handler, reducer hoặc key-based reset không?
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.