Skip to content
Home
Go back

Component Lifecycle trong React: Mounting, Updating, Unmounting

Mục lục

Xem mục lục

Lifecycle là gì

Lifecycle (vòng đời) là các giai đoạn một component React trải qua từ lúc được tạo ra trên DOM, cập nhật khi state/props thay đổi, cho tới lúc bị gỡ bỏ. Có 3 giai đoạn chính:

Class component có các lifecycle method riêng cho từng giai đoạn, gọi tự động theo đúng thứ tự. Function component không có các method riêng biệt, dùng useEffect để mô phỏng lại cả 3 giai đoạn trong cùng một hook.

Mounting: component được tạo ra

Class component, thứ tự gọi:

constructor → render → componentDidMount
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    console.log("Component đã được chèn vào DOM");
  }

  render() {
    return <p>{this.state.count}</p>;
  }
}

Function component, dùng useEffect với dependency array rỗng:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Component đã được chèn vào DOM");
  }, []);

  return <p>{count}</p>;
}

Dependency array rỗng ([]) báo cho React biết effect này chỉ cần chạy một lần, ngay sau lần render đầu tiên, tương đương componentDidMount.

Updating: component render lại

Class component:

render → componentDidUpdate
componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    console.log("count đã đổi thành", this.state.count);
  }
}

componentDidUpdate nhận prevProps/prevState để so sánh thủ công, tự kiểm tra xem giá trị nào đã thay đổi.

Function component, dùng useEffect với dependency cụ thể:

useEffect(() => {
  console.log("count đã đổi thành", count);
}, [count]);

React tự so sánh giá trị trong dependency array giữa các lần render, chỉ chạy lại effect khi giá trị đó thay đổi, không cần tự viết logic so sánh như componentDidUpdate.

Unmounting: component bị gỡ bỏ

Class component:

componentWillUnmount() {
  console.log("Component sắp bị gỡ khỏi DOM");
}

Function component, dùng cleanup function trả về từ useEffect:

useEffect(() => {
  console.log("Component đã mount");

  return () => {
    console.log("Component sắp bị gỡ khỏi DOM");
  };
}, []);

Function trả về từ useEffect chạy vào hai thời điểm: ngay trước khi component unmount, hoặc ngay trước khi effect chạy lại ở lần tiếp theo (nếu dependency thay đổi). Đây là điểm khác biệt so với componentWillUnmount, vốn chỉ gọi đúng một lần khi unmount.

Bảng đối chiếu class lifecycle method và useEffect

Giai đoạnClass componentFunction component
MountingcomponentDidMountuseEffect(() => {...}, [])
UpdatingcomponentDidUpdate(prevProps, prevState)useEffect(() => {...}, [dependency])
UnmountingcomponentWillUnmountCleanup function: return () => {...} trong useEffect
So sánh giá trị cũ/mớiTự so sánh thủ công qua prevProps/prevStateReact tự so sánh dependency array

Vì sao dễ gây memory leak nếu quên cleanup

Một effect đăng ký thứ gì đó tồn tại lâu dài (subscription, timer, event listener) nhưng không dọn dẹp khi unmount sẽ khiến thứ đó tiếp tục tồn tại, dù component đã biến mất khỏi DOM:

useEffect(() => {
  const timer = setInterval(() => {
    console.log("tick");
  }, 1000);

  // Thiếu cleanup: interval vẫn chạy mãi kể cả sau khi component unmount
}, []);

Cách sửa: luôn trả về function dọn dẹp tương ứng với thứ đã đăng ký:

useEffect(() => {
  const timer = setInterval(() => {
    console.log("tick");
  }, 1000);

  return () => clearInterval(timer);
}, []);

Nguyên tắc chung: mọi effect đăng ký thứ gì đó (subscription, timer, event listener, WebSocket…) đều nên có cleanup tương ứng, trừ khi thứ đó tự kết thúc ngay trong effect (như một lần fetch dữ liệu không cần hủy).

Cạm bẫy: set state sau khi component đã unmount

Gọi setState/setter của useState sau khi component đã unmount gây cảnh báo quen thuộc: “Can’t perform a React state update on an unmounted component”. Tình huống điển hình: fetch dữ liệu trong effect, nhưng component unmount trước khi request kịp trả về:

useEffect(() => {
  fetchUser(userId).then(data => {
    setUser(data); // Nếu component đã unmount trước khi fetch xong, gây cảnh báo
  });
}, [userId]);

Cách tránh phổ biến nhất: dùng AbortController để hủy request khi unmount, đồng thời tránh luôn việc gọi setState sau khi đã hủy:

useEffect(() => {
  const controller = new AbortController();

  fetchUser(userId, { signal: controller.signal })
    .then(data => setUser(data))
    .catch(err => {
      if (err.name !== "AbortError") throw err;
    });

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

Cách này vừa tránh warning, vừa tránh lãng phí: request không còn cần thiết được hủy thật sự ở tầng network, không chỉ đơn thuần bỏ qua kết quả.

Tổng kết


Share this post:

Previous Post
Validate Form với React Hook Form và Zod
Next Post
Optimistic Updates với React Query