Mặc dù là single page application, tuy nhiên không vì thế mà không có sự chuyển đổi trang trong quá trình người dùng sử dụng ứng dụng web của chúng ta. Khi có sự chuyển đổi thì cũng là lúc chúng ta phải quan tâm đến vòng đời của một trang nói chung hay các thành phần nói riêng để có phương án xử lý cho việc khởi tạo hoặc huỷ bỏ các dữ liệu hoặc các sự kiện không còn được sử dụng. Trong bài này Dũng sẽ cùng các bạn tìm hiểu vòng đời của một ReactJS component nhé.

Khởi tạo dự án

Đầu tiên chúng ta sẽ khởi tạo dự án component-lifecycle bằng cách sử dụng câu lệnh:

yarn create vite component-lifecycle

Bạn đừng quên lựa chọn framework là ReactJS và ngôn ngữ là Typescript nhé.
Sau khi bạn khởi tạo dự án xong hãy cd vào thư mục component-lifecycle vừa được tạo và gõ lệnh yarn để tải các thư viện cần thiết và chạy lệnh yarn dev để khởi chạy server và bạn có thể truy cập vào trang web thông qua địa chỉ: http://localhost:5173/

Vòng đời của một component

Vòng đời của một component trong react trải qua từ giai đoạn:

  1. Được khởi tạo thông qua hàm tạo: Chúng ta có thể khởi tạo các biến ban đầu, đăng ký sự kiện tại đây.
  2. Render để tạo HTML.
  3. Kích hoạt sự kiện componentDidMount: Chúng ta có thể gọi một số hàm lấy dữ liệu hoặc đăng ký nhận sự kiện tại đây.
    Khi có trạng thái được được cập nhật thì:
  4. Hàm shouldComponentUpdate để kiểm tra có cần nhật component hãy không sẽ được gọi, nếu hàm này trả về true thì hàm render sẽ được gọi.
  5. Sau khi hàm render được gọi thì sự kiện componentDidUpdate sẽ được gọi.
    Khi component con xảy ra lỗi thì component cha có thể nhận được sự kiện componentDidCatch.
    Khi một component không còn được sử dụng nữa, ví dụ như chuyển view chẳng hạn thì sự kiện componentWillUnmount sẽ xảy ra, bạn có thể sử dụng sự kiện này để huỷ các dữ liệu không còn được sử dụng hoặc bỏ đăng ký các sự kiện không còn được sử dụng nữa.

Mã nguồn ví dụ

Để có thể giúp bạn kiểm chứng tôi đã tạo ra 2 tập tin mới là Home.tsx và Welcome.tsx với mã nguồn như sau:
Tập tin Home.tsx:

import React from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

interface State {
  count: number;
  throwError: boolean;
}

interface HomeProps {
    parent: any;
  }

class Home extends React.Component<HomeProps> {

  private parent: any;

  constructor(props: any) {
    super(props)
    this.parent = props.parent;
    this.state = {
      count: 0,
      data: {count: 0, throwError: false}
    }
    console.log("Order 1: constructor");
  }

  componentDidMount(): void {
    console.log("Order 3: componentDidMount");
  }

  componentWillUnmount(): void {
    console.log("Order 6: componentWillUnmount");
  }

  shouldComponentUpdate(nextProps: Readonly<{}>, nextState: Readonly<{}>, nextContext: any): boolean {
    console.log("Order 4: shouldComponentUpdate");
    return true;
  }

  componentDidUpdate(prevProps: Readonly<{}>, prevState: Readonly<{}>, snapshot?: any): void {
    console.log("Order 5: componentDidUpdate");
  }

  setCount(count: number) {
    this.setState({
      count: count
    });
  }

  handleClickThrowError() {
    this.setState({ throwError: true });
  };

  handleNavigateClick() {
    this.parent.navigate("Wecome");
  }

  public render() {
    console.log("Order 2. render");
    const {count, throwError} = this.state as State;
    if (throwError) {
        throw new Error('Something went wrong!');
      }
    return (
      <>
        <div>
          <a href="https://vitejs.dev" target="_blank">
            <img src={viteLogo} className="logo" alt="Vite logo" />
          </a>
          <a href="https://react.dev" target="_blank">
            <img src={reactLogo} className="logo react" alt="React logo" />
          </a>
        </div>
        <h1>Vite + React</h1>
        <div className="card">
          <button onClick={() => this.setCount(count + 1)}>
            count is {count}
          </button>
          <button onClick={() => this.handleClickThrowError()}>Click to throw error</button>
          <button onClick={() => this.handleNavigateClick()}>Navigate</button>
          <p>
            Edit <code>src/App.tsx</code> and save to test HMR
          </p>
        </div>
        <p className="read-the-docs">
          Click on the Vite and React logos to learn more
        </p>
      </>
    )
  }
}

export default Home;

Tập tin Welcome.tsx

import React from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

class Welcome extends React.Component {

  constructor(props: any) {
    super(props)
    this.state = {
      count: 0,
      data: {count: 0, throwError: false}
    }
  }

  public render() {
    return (
      <>
        <div>
          <a href="https://vitejs.dev" target="_blank">
            <img src={viteLogo} className="logo" alt="Vite logo" />
          </a>
          <a href="https://react.dev" target="_blank">
            <img src={reactLogo} className="logo react" alt="React logo" />
          </a>
        </div>
        <h1>Vite + React</h1>
        <div className="card">
          <h1>Welcome</h1>
          <p>
            Edit <code>src/App.tsx</code> and save to test HMR
          </p>
        </div>
        <p className="read-the-docs">
          Click on the Vite and React logos to learn more
        </p>
      </>
    )
  }
}

export default Welcome;

Tập tin App.tsx sẽ được cập nhật lại thành như sau:

import React from 'react';
import './App.css';
import Home from './Home';
import Welcome from './Welcome';

interface State {
  view: string;
}

class App extends React.Component {

  constructor(props: any) {
    super(props)
    this.state = {};
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
    console.log("componentDidCatch");
  }

  navigate() {
    this.setState({view: "Welcome"});
  }

  public render() {
    const {view} = this.state as State;
    return (
      (view == "Welcome")
        ? <Welcome />
        : <Home parent={this} />
    )
  }
}

export default App

Tập tin main.tsx cũng được thay đổi thành:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.Fragment>
    <App />
  </React.Fragment>,
)

Bây giờ bạn hãy quay trở lại trình duyệt với tab http://localhost:5173/ và inspect để thấy được màn hình console log, sau đó bạn hãy refresh lại và kết quả bạn nhận được sẽ như sau:

Một màn hình mới và 3 dòng log:

Order 1: constructor
Order 2. render
Order 3: componentDidMount

Tiếp theo bạn có thể nhấn vào nút count is, nó sẽ tăng lên và có log mới được sinh ra:

Order 1: constructor
Order 2. render
Order 3: componentDidMount
Order 4: shouldComponentUpdate
Order 2. render
Order 5: componentDidUpdate

Bạn có thể nhấn nút Click to throw error và sẽ có log kiểu này được in ra:

Order 4: shouldComponentUpdate
Home.tsx:61 Order 2. render
()
Home.tsx:64 Uncaught 
Error: Something went wrong!
    at Home.render (Home.tsx:64:15)
Home.tsx:38 Order 4: shouldComponentUpdate
Home.tsx:61 Order 2. render
Home.tsx:38 Order 4: shouldComponentUpdate
Home.tsx:61 Order 2. render
()
Home.tsx:64 Uncaught 
Error: Something went wrong!
    at Home.render (Home.tsx:64:15)
Home.tsx:34 Order 6: componentWillUnmount
()
console.js:288 The above error occurred in the <Home> component:

    at Home (http://localhost:5173/src/Home.tsx?t=1721209923900:10:5)
    at App (http://localhost:5173/src/App.tsx?t=1721209923900:10:5)

React will try to recreate this component tree from scratch using the error boundary you provided, App.
App.tsx:18 componentDidCatch
()
console.js:288 Warning: App: Error boundaries should implement getDerivedStateFromError(). In that method, return a state update to display an error message or fallback UI.
    at App (http://localhost:5173/src/App.tsx?t=1721209923900:10:5)

Hoặc khi bạn nhấn nút Navigate sẽ thấy Order 3: componentWillUnmount được in ra.

### Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu về vòng đời của một react component, tiếp theo chúng ta sẽ cùng tìm hiểu về React hook nhé.


Cám ơn bạn đã quan tâm đến bài viết này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a