Tương đương là hàm cleanup trả về từ useEffect — nhưng nó chạy nhiều hơn componentWillUnmount: trước mỗi lần effect chạy lại, chứ không chỉ lúc unmount.
jsx
// Class — chỉ chạy khi component bị gỡ
componentWillUnmount() { clearInterval(this.timerId) }
// Hook — chạy trước mỗi lần effect chạy lại, VÀ khi unmount
useEffect(() => {
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])Khác biệt này là điểm cộng chứ không phải phiền toái: nó ép bạn viết phần huỷ ngay cạnh phần đăng ký, nên khó quên hơn hẳn so với việc nhớ bổ sung vào một method nằm cách đó vài chục dòng.
Bỏ quên cleanup rò rỉ theo ba đường:
- Timer và listener tích tụ. Mỗi lần effect chạy lại tạo thêm một cái nữa, cái cũ vẫn sống. Đồng hồ đếm nhanh dần, handler chạy nhiều lần cho một sự kiện.
- Bộ nhớ không được thu hồi. Listener còn sống giữ closure, closure giữ state và cả cây DOM mà nó tham chiếu, nên component đã gỡ khỏi màn hình vẫn nằm trong bộ nhớ.
- Request không huỷ. Request cũ trả về rồi gọi setter của component đã unmount — React bỏ qua cập nhật đó và từ React 18 cũng không còn cảnh báo, nên nó chỉ tốn băng thông. Nguy hiểm hơn là khi component vẫn còn trên màn hình và effect chạy lại vì dependency đổi: response cũ về sau và ghi đè response mới.
Lưu ý: Trong class, gọi setState ở componentWillUnmount là vô nghĩa vì component sắp bị gỡ. Trong hook, phần cleanup cũng vậy — nó dùng để huỷ, không dùng để cập nhật giao diện.