React Component Lifecycle: Tổng hợp những kiến thức bạn cần biết

VnnTools

Tưởng tượng mỗi component trong ứng dụng React của bạn là một "sinh vật sống". Nó được "sinh ra" (khởi tạo và thêm vào giao diện), "lớn lên" (cập nhật khi có dữ liệu mới), và cuối cùng "biến mất" (khi không còn cần thiết). Toàn bộ quá trình này được gọi là vòng đời của component (Component Lifecycle).

React Component Lifecycle: Tổng hợp những kiến thức bạn cần biết

Hiểu rõ vòng đời này không chỉ là một kiến thức lý thuyết - đó là chìa khóa để bạn kiểm soát hoàn toàn ứng dụng của mình, từ việc lấy dữ liệu đúng lúc, tối ưu hóa hiệu năng, đến việc dọn dẹp tài nguyên để tránh rò rỉ bộ nhớ. Bài viết này sẽ dẫn bạn đi qua từng giai đoạn, từ khái niệm cổ điển trong Class Component đến kỷ nguyên hiện đại của Hooks. 🚀

React Lifecycle là gì? Một cách nhìn trực quan

React Lifecycle là một chuỗi các phương thức (hoặc Hooks) được tự động gọi theo một thứ tự cụ thể trong suốt cuộc đời của một component. Nó cung cấp cho chúng ta những "điểm neo" để thực thi mã lệnh tại các thời điểm quan trọng.

Vòng đời của một component được chia thành ba giai đoạn chính:

React Component Lifecycle là gì?

  1. Khởi tạo (Mounting): Giai đoạn component được tạo ra, các giá trị state và props ban đầu được thiết lập, và nó được "gắn" vào cây DOM của trình duyệt. Đây là lúc component lần đầu tiên xuất hiện trên màn hình.
  2. Cập nhật (Updating): Giai đoạn này xảy ra khi state hoặc props của component thay đổi. React sẽ render lại component để phản ánh những thay đổi này lên giao diện người dùng.
  3. Gỡ bỏ (Unmounting): Giai đoạn cuối cùng, khi component bị xóa khỏi cây DOM. Đây là cơ hội cuối cùng để thực hiện các hành động dọn dẹp.

Việc hiểu rõ ba giai đoạn này giúp bạn trả lời các câu hỏi quan trọng như: "Tôi nên gọi API để lấy dữ liệu ở đâu?", "Làm thế nào để tối ưu việc render lại?", "Khi nào tôi cần hủy một subscription?".

Class Components và Functional Components (với Hooks)

Lịch sử phát triển của React đã tạo ra hai cách tiếp cận chính để làm việc với lifecycle.

Kỷ nguyên "cổ điển" - Class Components

Trước khi Hooks ra đời, lifecycle được quản lý thông qua các phương thức đặc biệt trong Class Component. Dù hiện nay ít được sử dụng khi viết code mới, bạn vẫn sẽ gặp chúng rất nhiều trong các dự án cũ.

Các phương thức quan trọng nhất bao gồm:

  • constructor(): Nơi khởi tạo state và bind các phương thức. Được gọi đầu tiên, chỉ một lần duy nhất.
  • render(): Phương thức duy nhất bắt buộc. Nó đọc this.propsthis.state để trả về JSX, mô tả giao diện sẽ trông như thế nào.
  • componentDidMount(): Được gọi ngay sau khi component được render và gắn vào DOM. Đây là nơi lý tưởng để gọi API, thiết lập các subscription, hoặc tương tác với DOM.
  • componentDidUpdate(prevProps, prevState): Được gọi ngay sau khi component được cập nhật (render lại). Không được gọi trong lần render đầu tiên. Hữu ích để thực thi các tác vụ phụ khi props hoặc state thay đổi.
  • componentWillUnmount(): Được gọi ngay trước khi component bị gỡ bỏ khỏi DOM. Đây là nơi bắt buộc phải dọn dẹp: hủy timer, hủy subscription, hủy các request mạng đang chờ...

Ví dụ với Class Component:

import React, { Component } from 'react'

class MyClock extends Component {
  constructor(props) {
    super(props)
    this.state = { date: new Date() }
    console.log('1. Constructor: Khởi tạo state')
  }

  componentDidMount() {
    console.log('3. ComponentDidMount: Gắn vào DOM, bắt đầu timer')
    this.timerID = setInterval(() => this.tick(), 1000)
  }

  componentWillUnmount() {
    console.log('4. ComponentWillUnmount: Dọn dẹp timer')
    clearInterval(this.timerID)
  }

  tick() {
    this.setState({ date: new Date() })
  }

  render() {
    console.log('2. Render: Vẽ giao diện')
    return <h2>Bây giờ là {this.state.date.toLocaleTimeString()}.</h2>
  }
}

Kỷ nguyên "hiện đại" - Functional Components và Hooks

Với sự ra đời của React Hooks, cách chúng ta tương tác với lifecycle đã thay đổi một cách ngoạn mục. Thay vì nhiều phương thức khác nhau, giờ đây chúng ta chủ yếu sử dụng một Hook duy nhất và cực kỳ mạnh mẽ: useEffect.

useEffect cho phép bạn thực hiện các "tác vụ phụ" (side effects) trong functional component. Nó có thể đảm nhiệm vai trò của cả componentDidMount, componentDidUpdate, và componentWillUnmount.

Cú pháp: useEffect(callback, [dependencies])

  • callback: Một hàm chứa mã lệnh của tác vụ phụ.
  • [dependencies] (mảng phụ thuộc): Quyết định khi nào callback sẽ được chạy lại. Đây chính là "bộ não" của useEffect.

Cách useEffect ánh xạ tới các giai đoạn lifecycle:

  1. Mô phỏng componentDidMount (Chạy 1 lần khi khởi tạo):

    • Sử dụng một mảng phụ thuộc rỗng [].
    • Khi nào dùng: Gọi API lấy dữ liệu ban đầu.
useEffect(() => {
  console.log('Component đã được gắn vào DOM')
  // Gọi API ở đây
}, []) // Mảng rỗng -> chỉ chạy 1 lần
  1. Mô phỏng componentDidUpdate (Chạy khi có sự thay đổi):

    • Cung cấp các giá trị state hoặc props vào mảng phụ thuộc. Effect sẽ chạy lại mỗi khi một trong các giá trị này thay đổi.
    • Khi nào dùng: Gọi lại API khi một ID thay đổi, cập nhật giao diện dựa trên prop mới.
useEffect(() => {
  console.log(`User ID đã thay đổi thành: ${userId}`)
  // Gọi API để lấy thông tin user mới với userId
}, [userId]) // Chạy lại mỗi khi userId thay đổi
  1. Mô phỏng componentWillUnmount (Hàm dọn dẹp):

    • Return một hàm từ bên trong callback của useEffect. Hàm return này sẽ được gọi trước khi component bị gỡ bỏ, hoặc trước khi effect chạy lại.
    • Khi nào dùng: Rất quan trọng! Dùng để hủy subscription, timer, event listener...
useEffect(() => {
  // Thiết lập tác vụ
  const timerId = setInterval(myFunction, 1000)

  // Hàm dọn dẹp
  return () => {
    console.log('Dọn dẹp timer')
    clearInterval(timerId)
  }
}, [])

Các ví dụ thực tế và tình huống sử dụng

Hiểu lý thuyết là một chuyện, áp dụng vào thực tế mới là điều quan trọng.

Ví dụ 1: Lấy dữ liệu từ API

Đây là trường hợp sử dụng useEffect phổ biến nhất.

import React, { useState, useEffect } from 'react'

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    setLoading(true)
    fetch(`https://api.example.com/users/${userId}`)
      .then((res) => res.json())
      .then((data) => {
        setUser(data)
        setLoading(false)
      })

    // Hàm dọn dẹp này hữu ích nếu component bị unmount
    // trước khi fetch hoàn tất, giúp tránh lỗi setState trên unmounted component.
    return () => {
      // Bạn có thể dùng AbortController để hủy request tại đây
    }
  }, [userId]) // Lấy lại dữ liệu mỗi khi userId thay đổi

  if (loading) return <p>Đang tải...</p>
  if (!user) return <p>Không tìm thấy người dùng.</p>

  return <h1>{user.name}</h1>
}

Ví dụ 2: Lắng nghe sự kiện của trình duyệt

Ví dụ, theo dõi kích thước cửa sổ để làm giao diện responsive.

import React, { useState, useEffect } from 'react'

function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth)

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth)

    // Đăng ký event listener khi component mount
    window.addEventListener('resize', handleResize)
    console.log("Đã đăng ký event listener 'resize'")

    // Hủy đăng ký khi component unmount
    return () => {
      window.removeEventListener('resize', handleResize)
      console.log("Đã hủy event listener 'resize'")
    }
  }, []) // Chỉ cần chạy 1 lần

  return <p>Chiều rộng cửa sổ: {width}px</p>
}

🧠 Lời khuyên và những cạm bẫy cần tránh

  1. ⚠️ Vòng lặp vô hạn (Infinite Loop): Cạm bẫy phổ biến nhất với useEffect. Nếu bạn setState bên trong một effect mà lại không cung cấp mảng phụ thuộc, hoặc phụ thuộc vào chính state đó mà không có điều kiện dừng, bạn sẽ tạo ra một vòng lặp render -> effect -> setState -> render...

    • Cách tránh: Luôn cung cấp mảng phụ thuộc. Hãy suy nghĩ kỹ về những gì bạn đưa vào mảng này.
  2. Luôn có hàm dọn dẹp: Nếu bạn đã thiết lập một thứ gì đó cần "hủy" (timer, subscription, event listener), hãy luôn luôn return một hàm dọn dẹp trong useEffect. Việc này giúp ngăn ngừa rò rỉ bộ nhớ và các lỗi không mong muốn.

  3. 💡 Quy tắc về mảng phụ thuộc: Hãy bao gồm tất cả các giá trị (props, state, hàm) từ bên ngoài mà effect của bạn sử dụng vào mảng phụ thuộc. Plugin ESLint react-hooks/exhaustive-deps sẽ giúp bạn tự động kiểm tra việc này.

Kết luận: Nắm vững "nhịp điệu" của React

React Lifecycle không phải là một khái niệm khô khan. Nó chính là nhịp điệu, là dòng chảy của ứng dụng. Bằng cách nắm vững các giai đoạn Mounting, Updating, và Unmounting - và quan trọng hơn là cách điều khiển chúng thông qua useEffect - bạn sẽ chuyển từ một người "viết" React sang một người "kiến tạo" các ứng dụng React hiệu quả, mạnh mẽ và không có lỗi.

Hãy coi useEffect như một người bạn đồng hành đa năng, giúp bạn thực thi đúng mã, vào đúng thời điểm. Nắm vững nó, và bạn đã nắm vững một trong những phần cốt lõi và đẹp đẽ nhất của React.

Bài viết liên quan

React làm được gì? Nhũng ứng dụng nổi bật nhất của React

Bạn có biết React làm được những gì không? Từ xây dựng giao diện tương tác đến các ứng dụng phức tạp, bài viết này sẽ khám phá những ứng dụng thực tế và lợi ích của React.

React Hook là gì? Hướng dẫn chi tiết cho người mới bắt đầu

Bạn đang tìm hiểu về React Hook? Bài viết này sẽ giải thích React Hook là gì, các loại Hook phổ biến và hướng dẫn từng bước cách áp dụng chúng vào dự án thực tế.

Component React là gì? Hướng dẫn chi tiết cho người mới bắt đầu

Hiểu rõ Component React chính là chìa khóa để xây dựng giao diện ứng dụng web hiện đại. Đọc ngay để nắm vững các loại component phổ biến và cách tạo component đầu tiên của bạn.

React Router là gì? Hướng dẫn toàn tập cho người mới bắt đầu

React Router là gì? Tìm hiểu cách React Router giúp bạn xây dựng ứng dụng một trang (SPA) mượt mà. Hướng dẫn chi tiết từ cài đặt cơ bản đến các tính năng nâng cao.