Họ đang đo gì
Bạn có phân biệt được “tính ra từ dữ liệu hiện có” với “đồng bộ với thế giới bên ngoài” không.
Trả lời ngắn~30 giây
useEffect là để đồng bộ với thứ nằm ngoài React: đăng ký sự kiện DOM, kết nối WebSocket, gọi API bên thứ ba, đặt title trang. Nếu bạn có thể TÍNH RA giá trị trong lúc render thì đừng dùng effect — hãy tính thẳng. Effect chỉ để cập nhật state dựa trên props gây thêm một vòng render, và trong React 18 với StrictMode nó còn chạy hai lần ở dev, khiến bug lộ ra theo cách khó hiểu.
Giải thích sâu
Mẫu sai phổ biến nhất là “state dẫn xuất”: có items và query, rồi một effect lọc items và ghi vào filtered. Nó tạo hai lần render mỗi lần gõ phím, mở ra cửa sổ mà filtered chưa khớp với items, và thêm một nguồn sự thật thứ hai có thể lệch. Chỉ cần const filtered = items.filter(…) ngay trong thân component; nếu phép lọc thật sự đắt thì bọc useMemo, nhưng chỉ sau khi đo.
Mẫu sai thứ hai là dùng effect để phản ứng với sự kiện người dùng: bấm nút thì đặt một state submitted, rồi một effect thấy submitted đổi và gửi request. Cái này biến một hành động rõ ràng thành một chuỗi nhân quả phải đọc ngược. Logic thuộc về sự kiện thì viết trong handler của sự kiện đó — đơn giản hơn, dễ đọc hơn, và không phụ thuộc vào chu kỳ render.
Về StrictMode chạy hai lần: nó không phải bug và không xảy ra ở production. Nó cố tình mount–unmount–mount để phơi bày effect thiếu hàm dọn dẹp. Nếu effect của bạn chạy hai lần mà gây vấn đề, gần như chắc chắn bạn đang thiếu cleanup — và cùng vấn đề đó sẽ xảy ra ở production khi component mount lại vì lý do khác.
// Không cần effect: tính thẳng khi render
const filtered = items.filter((i) => i.name.includes(query));
// Cần effect: đồng bộ với thứ ngoài React, và có dọn dẹp
useEffect(() => {
const socket = new WebSocket(url);
socket.onmessage = onMessage;
return () => socket.close(); // <- StrictMode phơi bày nếu thiếu
}, [url]);Câu hỏi tiếp theo họ sẽ hỏi
?Fetch dữ liệu trong useEffect có sao không?
Nó chạy được nhưng bạn tự viết lại: race condition khi tham số đổi nhanh, huỷ request, cache, retry, trạng thái loading. Đó chính là những thứ TanStack Query hoặc data loader của framework đã giải. Trong Next.js App Router thì tốt nhất là fetch ở Server Component và không có effect nào cả.
?useLayoutEffect khác gì?
Nó chạy ĐỒNG BỘ sau khi DOM cập nhật nhưng TRƯỚC khi trình duyệt vẽ, nên dùng khi bạn phải đo kích thước rồi chỉnh vị trí mà không muốn người dùng thấy nhấp nháy. Đổi lại nó chặn việc vẽ, nên đặt logic nặng ở đó là cách chắc chắn làm INP xấu đi.
Trả lời thế này là mất điểm
- Dùng effect để đồng bộ hai state với nhau. Đó là dấu hiệu một trong hai không nên là state.
- Tắt StrictMode vì effect chạy hai lần. Bạn vừa tắt cái đang báo cho bạn biết có bug.