Bài 16.4: Lifecycle methods trong class components trong React-TypeScript

Chào mừng trở lại hành trình chinh phục Front-end hiện đại! Hôm nay, chúng ta sẽ cùng nhau đi sâu vào một khái niệm cực kỳ quan trọng khi làm việc với React class components: Lifecycle Methods. Đây chính là những "điểm móc" (hooks) mà React cung cấp cho phép chúng ta chạy code tại những thời điểm cụ thể trong "vòng đời" của một component, từ lúc nó được tạo ra, cập nhật, cho đến khi nó biến mất khỏi màn hình.

Mặc dù React Hooks đã trở nên phổ biến và là cách tiếp cận chính trong functional components hiện nay, việc hiểu rõ lifecycle methods của class components vẫn vô cùng giá trị. Nó giúp bạn đọc và duy trì các codebase cũ, đồng thời cung cấp nền tảng kiến thức vững chắc về cách React quản lý các component. Và tất nhiên, chúng ta sẽ áp dụng sức mạnh của TypeScript để đảm bảo code của chúng ta an toàndễ bảo trì hơn.

Hãy cùng nhau khám phá từng giai đoạn trong vòng đời của một class component và cách các lifecycle methods "thức dậy" tại mỗi chặng đường đó!

React class components có ba giai đoạn chính trong vòng đời của chúng:

  1. Mounting (Khởi tạo): Component được tạo ra và "gắn" (mount) vào DOM.
  2. Updating (Cập nhật): Component được render lại do props hoặc state thay đổi.
  3. Unmounting (Gỡ bỏ): Component bị "gỡ" (unmount) khỏi DOM và bị hủy.

Ngoài ra, còn có các phương thức xử lý lỗi (Error Handling).

Hãy cùng đi chi tiết vào từng giai đoạn!

Giai đoạn 1: Mounting (Khởi tạo)

Đây là lúc component của bạn lần đầu tiên được sinh ra và xuất hiện trên màn hình. Các methods theo thứ tự gọi trong giai đoạn này là:

  1. constructor()
  2. static getDerivedStateFromProps()
  3. render()
  4. componentDidMount()
constructor(props)

Đây là phương thức đầu tiên được gọi khi một component được tạo ra. Nó thường được sử dụng cho hai mục đích chính:

  • Khởi tạo local state của component bằng cách gán trực tiếp một object vào this.state.
  • Liên kết (bind) các event handler methods với instance của class (mặc dù dùng arrow functions trong class properties là cách hiện đại hơn và không cần bind thủ công).

Lưu ý: Trong constructor, bạn không nên gọi setState(). Thay vào đó, hãy gán trực tiếp giá trị ban đầu cho this.state. Bạn phải gọi super(props) trước bất cứ thứ gì khác trong constructor.

Ví dụ với TypeScript:

import React, { Component } from 'react';

interface MyComponentProps {
  initialMessage: string;
}

interface MyComponentState {
  message: string;
  count: number;
}

class MyComponent extends Component<MyComponentProps, MyComponentState> {
  constructor(props: MyComponentProps) {
    super(props); // <-- Bắt buộc phải gọi

    // Khởi tạo state ban đầu
    this.state = {
      message: props.initialMessage || 'Hello, World!',
      count: 0
    };

    // Binding method (ít dùng với arrow function class properties)
    // this.handleClick = this.handleClick.bind(this);
  }

  // ... các methods và render() ở đây
}

Giải thích: Chúng ta định nghĩa types cho propsstate bằng các interface MyComponentPropsMyComponentState. Component class kế thừa từ Component<MyComponentProps, MyComponentState> để thông báo cho TypeScript biết kiểu dữ liệu của propsstate. Trong constructor, super(props) được gọi, sau đó this.state được gán giá trị khởi tạo dựa trên props.

static getDerivedStateFromProps(props, state)

Phương thức này được gọi ngay sau constructor (ở giai đoạn mounting) và mỗi khi component nhận được props mới hoặc setState được gọi (ở giai đoạn updating).

Đây là một static method, nghĩa là nó không có quyền truy cập vào instance của component (this). Mục đích duy nhất của nó là cho phép component cập nhật state nội bộ dựa trên sự thay đổi của props.

Nó trả về một object để cập nhật state, hoặc null nếu không cần cập nhật state. Phương thức này khá ít dùng và thường chỉ được sử dụng trong các trường hợp state của component cần phản ánh trực tiếp một phần của props một cách có điều kiện.

Lưu ý: Sử dụng phương thức này có thể khiến code khó hiểu hơn. Thường có những cách đơn giản hơn để quản lý state dựa trên props (ví dụ: tính toán giá trị trong render hoặc componentDidUpdate).

Ví dụ với TypeScript:

import React, { Component } from 'react';

interface GreetingProps {
  name: string;
}

interface GreetingState {
  greetingMessage: string;
}

class Greeting extends Component<GreetingProps, GreetingState> {
  constructor(props: GreetingProps) {
    super(props);
    this.state = {
      greetingMessage: `Hello, ${props.name}!`
    };
  }

  static getDerivedStateFromProps(nextProps: GreetingProps, prevState: GreetingState): GreetingState | null {
    // Nếu tên thay đổi, cập nhật lời chào trong state
    if (nextProps.name !== prevState.greetingMessage.substring(7, prevState.greetingMessage.length - 1)) {
      return { greetingMessage: `Hello, ${nextProps.name}!` };
    }
    return null; // Không cần cập nhật state
  }

  render() {
    return <h1>{this.state.greetingMessage}</h1>;
  }
}

Giải thích: getDerivedStateFromProps là static và nhận nextProps cùng prevState. Nó kiểm tra xem nextProps.name có khác với tên trong prevState.greetingMessage hay không. Nếu có, nó trả về một object mới { greetingMessage: ... } để cập nhật state. Nếu không, nó trả về null.

render()

Đây là phương thức duy nhất bắt buộc trong class components. Nó chịu trách nhiệm mô tả UI mà component sẽ hiển thị.

Phương thức render() đọc this.propsthis.state và trả về một trong những thứ sau:

  • Một React element (thường là JSX).
  • Một mảng hoặc fragment chứa nhiều elements.
  • Strings hoặc numbers.
  • Portals.
  • null hoặc false (để không render gì cả).

Lưu ý: Phương thức render() phải là pure function (hàm thuần khiết). Điều này có nghĩa là nó không được gây ra bất kỳ side effects (tác dụng phụ) nào, như gọi API, thay đổi state, hoặc tương tác trực tiếp với DOM. Nó phải luôn trả về kết quả giống nhau với cùng một tập hợp props và state.

Ví dụ với TypeScript:

import React, { Component } from 'react';

interface CounterProps {
  step: number;
}

interface CounterState {
  value: number;
}

class Counter extends Component<CounterProps, CounterState> {
  constructor(props: CounterProps) {
    super(props);
    this.state = { value: 0 };
  }

  increment = () => { // Sử dụng arrow function để không cần bind
    this.setState({ value: this.state.value + this.props.step });
  }

  render() {
    console.log('Render called!'); // Side effect nhẹ để debug, nhưng tránh trong production
    return (
      <div>
        <p>Current value: {this.state.value}</p>
        <button onClick={this.increment}>Increment by {this.props.step}</button>
      </div>
    );
  }
}

Giải thích: Phương thức render trả về JSX mô tả giao diện của bộ đếm. Nó truy cập this.state.valuethis.props.step. Hàm increment sử dụng this.setState để cập nhật state, và render sẽ được gọi lại sau khi state thay đổi.

componentDidMount()

Đây là phương thức được gọi ngay sau khi component và tất cả các components con của nó đã được render lần đầu tiên và "gắn" vào DOM.

Đây là nơi lý tưởng để thực hiện các side effects cần thiết sau khi component đã sẵn sàng trên giao diện:

  • Thực hiện các API calls để tải dữ liệu.
  • Thiết lập subscriptions (ví dụ: WebSocket).
  • Thêm các event listeners (ví dụ: lắng nghe sự kiện cuộn trang).
  • Thao tác trực tiếp với các DOM nodes nếu cần thiết (ví dụ: đo kích thước element).

Ví dụ với TypeScript:

import React, { Component } from 'react';

interface DataDisplayState {
  data: string | null;
  loading: boolean;
}

class DataDisplay extends Component<{}, DataDisplayState> { // Component không nhận props
  constructor(props: {}) {
    super(props);
    this.state = {
      data: null,
      loading: true
    };
  }

  componentDidMount() {
    console.log('componentDidMount: Fetching data...');
    // Simulate fetching data from an API
    setTimeout(() => {
      this.setState({
        data: 'Data fetched successfully!',
        loading: false
      });
      console.log('componentDidMount: Data fetched.');
    }, 2000);

    // Example: Add a global event listener (remember to clean up!)
    window.addEventListener('resize', this.handleResize);
  }

  handleResize = () => {
    console.log('Window resized!');
  }

  render() {
    if (this.state.loading) {
      return <p>Loading data...</p>;
    }
    return (
      <div>
        <p>{this.state.data}</p>
      </div>
    );
  }

  // ... componentWillUnmount method for cleanup
}

Giải thích: Trong componentDidMount, chúng ta mô phỏng việc gọi API bằng setTimeout. Sau khi "dữ liệu" về, chúng ta dùng this.setState để cập nhật state, khiến component render lại và hiển thị dữ liệu. Đây cũng là nơi chúng ta thêm window.addEventListener.

Giai đoạn 2: Updating (Cập nhật)

Giai đoạn này xảy ra khi component đã được mount và có sự thay đổi về props hoặc state. Các methods theo thứ tự gọi khi component cập nhật là:

  1. static getDerivedStateFromProps() (được gọi lại)
  2. shouldComponentUpdate()
  3. render() (được gọi lại)
  4. getSnapshotBeforeUpdate()
  5. componentDidUpdate()
static getDerivedStateFromProps(props, state)

Như đã đề cập ở trên, phương thức này được gọi lại ở đầu giai đoạn cập nhật, ngay cả khi setState được gọi trong component. Mục đích và cách sử dụng tương tự như ở giai đoạn mounting.

shouldComponentUpdate(nextProps, nextState)

Phương thức này được gọi trước khi component render lại do nhận props hoặc state mới. Mặc định, nó luôn trả về true, khiến component luôn render lại khi có thay đổi.

Mục đích chính của shouldComponentUpdatetối ưu hiệu năng. Bằng cách so sánh nextPropsnextState với this.propsthis.state, bạn có thể quyết định liệu React có cần tiếp tục quá trình render lại hay không. Nếu nó trả về false, phương thức render(), getSnapshotBeforeUpdate(), và componentDidUpdate() sẽ bị bỏ qua.

Lưu ý: Sử dụng phương thức này cần cẩn thận vì nó có thể dẫn đến lỗi nếu không so sánh tất cả các props và state liên quan. React cung cấp React.PureComponent như một cách đơn giản hơn để thực hiện shallow comparison tự động, thường đủ cho hầu hết các trường hợp. Với Hooks, bạn có React.memouseMemo.

Ví dụ với TypeScript:

import React, { Component, PureComponent } from 'react';

interface ItemListProps {
  items: string[];
}

interface ItemListState {
  // Có thể có state khác
}

// Cách 1: Tự implement shouldComponentUpdate
class ItemListManual extends Component<ItemListProps, ItemListState> {
  shouldComponentUpdate(nextProps: ItemListProps, nextState: ItemListState): boolean {
    // So sánh mảng items (so sánh tham chiếu)
    if (this.props.items !== nextProps.items) {
      console.log('ItemListManual: Items prop changed, updating.');
      return true;
    }
    // Nếu có state, so sánh state nữa
    // if (this.state !== nextState) return true;

    console.log('ItemListManual: Items prop did not change, skipping update.');
    return false; // Ngăn render lại nếu items không thay đổi
  }

  render() {
    console.log('ItemListManual: Render');
    return (
      <ul>
        {this.props.items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    );
  }
}

// Cách 2: Sử dụng PureComponent (thường dễ hơn)
class ItemListPure extends PureComponent<ItemListProps, ItemListState> {
    render() {
        console.log('ItemListPure: Render');
        return (
            <ul>
                {this.props.items.map((item, index) => (
                    <li key={index}>{item}</li>
                ))}
            </ul>
        );
    }
}

Giải thích: shouldComponentUpdate nhận nextPropsnextState. Chúng ta so sánh this.props.items với nextProps.items. Nếu chúng là các mảng khác nhau (khác tham chiếu), chúng ta trả về true. Nếu không (tham chiếu giống nhau, ngay cả khi nội dung bên trong giống nhau), chúng ta trả về false. PureComponent làm điều tương tự một cách tự động bằng cách so sánh sơ sài (shallow comparison) tất cả các props và state.

render()

Phương thức render() được gọi lại ở giai đoạn cập nhật nếu shouldComponentUpdate trả về true (hoặc nếu shouldComponentUpdate không tồn tại). Nó vẫn phải tuân thủ nguyên tắc là một pure function.

getSnapshotBeforeUpdate(prevProps, prevState)

Phương thức này được gọi ngay trước khi các thay đổi từ render() được "cam kết" (committed) vào DOM (tức là trước khi React cập nhật DOM thực tế).

Nó cho phép component chụp lại một số thông tin từ DOM (ví dụ: vị trí cuộn, kích thước element) trước khi DOM có khả năng bị thay đổi bởi quá trình cập nhật. Giá trị trả về của phương thức này sẽ được truyền làm tham số thứ ba cho componentDidUpdate().

Phương thức này khá hiếm khi được sử dụng và thường chỉ liên quan đến các trường hợp cần xử lý vị trí cuộn hoặc các chi tiết layout cụ thể.

Ví dụ với TypeScript:

import React, { Component, RefObject } from 'react';

interface ScrollListProps {
  items: string[];
}

interface ScrollListState {
  // ... state khác
}

class ScrollList extends Component<ScrollListProps, ScrollListState> {
  private listRef: RefObject<HTMLUListElement>; // Ref để truy cập DOM element

  constructor(props: ScrollListProps) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps: ScrollListProps, prevState: ScrollListState) {
    // Nếu có items mới được thêm vào cuối danh sách
    if (prevProps.items.length < this.props.items.length) {
      const list = this.listRef.current;
      if (list) {
        // Chụp lại chiều cao cuộn trước khi cập nhật DOM
        return list.scrollHeight - list.scrollTop;
      }
    }
    return null; // Không cần snapshot
  }

  componentDidUpdate(prevProps: ScrollListProps, prevState: ScrollListState, snapshot: number | null) {
    // snapshot là giá trị được trả về từ getSnapshotBeforeUpdate
    if (snapshot !== null) {
      const list = this.listRef.current;
      if (list) {
        // Điều chỉnh vị trí cuộn để giữ nguyên vị trí ban đầu so với cuối danh sách
        list.scrollTop = list.scrollHeight - snapshot;
      }
    }
  }

  render() {
    return (
      <ul ref={this.listRef} style={{ height: '200px', overflowY: 'scroll' }}>
        {this.props.items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    );
  }
}

Giải thích: Chúng ta sử dụng getSnapshotBeforeUpdate để tính toán khoảng cách từ đáy của danh sách đang cuộn trước khi React cập nhật DOM với items mới. Giá trị này được lưu vào snapshot. Trong componentDidUpdate, chúng ta sử dụng giá trị snapshot đó để điều chỉnh lại scrollTop của element DOM, giữ cho vị trí hiển thị của người dùng không bị nhảy.

componentDidUpdate(prevProps, prevState, snapshot)

Phương thức này được gọi ngay sau khi quá trình cập nhật đã hoàn thành và component đã được render lại trên DOM.

Đây là nơi lý tưởng để thực hiện các side effects phản ứng với sự thay đổi của props hoặc state:

  • Thực hiện các API calls dựa trên sự thay đổi của props (ví dụ: fetch dữ liệu người dùng mới khi userId prop thay đổi). Lưu ý: Phải thực hiện kiểm tra điều kiện (ví dụ: if (prevProps.userId !== this.props.userId)) để tránh lặp vô hạn.
  • Thao tác với DOM sau khi nó đã được cập nhật.
  • Cập nhật các subscriptions nếu cần thiết dựa trên props/state mới.

Ví dụ với TypeScript:

import React, { Component } from 'react';

interface UserProfileProps {
  userId: string;
}

interface UserProfileState {
  userData: any | null; // Định nghĩa kiểu dữ liệu chi tiết hơn nếu biết cấu trúc
  loading: boolean;
}

class UserProfile extends Component<UserProfileProps, UserProfileState> {
  constructor(props: UserProfileProps) {
    super(props);
    this.state = {
      userData: null,
      loading: false
    };
  }

  componentDidMount() {
    console.log(`componentDidMount: Fetching user ${this.props.userId}`);
    this.fetchUserData(this.props.userId);
  }

  componentDidUpdate(prevProps: UserProfileProps, prevState: UserProfileState) {
    console.log(`componentDidUpdate: Checking for user ID changes...`);
    // QUAN TRỌNG: Chỉ fetch lại nếu userId thay đổi!
    if (prevProps.userId !== this.props.userId) {
      console.log(`componentDidUpdate: userId changed from ${prevProps.userId} to ${this.props.userId}. Fetching new data.`);
      this.fetchUserData(this.props.userId);
    } else {
       console.log(`componentDidUpdate: userId is the same.`);
    }
    // snapshot parameter is also available here if getSnapshotBeforeUpdate is implemented
  }

  fetchUserData = (userId: string) => {
    this.setState({ loading: true, userData: null });
    // Simulate API call
    setTimeout(() => {
      this.setState({
        userData: { id: userId, name: `User ${userId}`, email: `${userId}@example.com` },
        loading: false
      });
    }, 1000);
  }

  render() {
    if (this.state.loading) {
      return <p>Loading user profile...</p>;
    }
    if (!this.state.userData) {
        return <p>No user data.</p>;
    }
    const { userData } = this.state;
    return (
      <div>
        <h2>User Profile</h2>
        <p>ID: {userData.id}</p>
        <p>Name: {userData.name}</p>
        <p>Email: {userData.email}</p>
      </div>
    );
  }
}

Giải thích: componentDidMount fetch dữ liệu lần đầu. componentDidUpdate kiểm tra xem this.props.userId có khác với prevProps.userId hay không. Chỉ khi có sự thay đổi này, chúng ta mới gọi lại fetchUserData. Điều này ngăn chặn việc fetch dữ liệu liên tục mỗi khi component cập nhật vì những lý do khác không liên quan đến userId.

Giai đoạn 3: Unmounting (Gỡ bỏ)

Giai đoạn này xảy ra khi component sắp bị xóa khỏi DOM. Chỉ có một phương thức duy nhất được gọi trong giai đoạn này:

componentWillUnmount()

Phương thức này được gọi ngay trước khi component bị gỡ bỏ và hủy bỏ khỏi DOM.

Đây là nơi cực kỳ quan trọng để thực hiện các tác vụ "dọn dẹp" (cleanup) để ngăn chặn memory leaks (rò rỉ bộ nhớ):

  • Hủy bỏ các subscriptions (ví dụ: đóng kết nối WebSocket).
  • Xóa bỏ các event listeners đã thêm trong componentDidMount.
  • Hủy bỏ các timers (ví dụ: setTimeout, setInterval).
  • Hủy bỏ các network requests đang chạy nếu có (nếu API hỗ trợ).

Ví dụ với TypeScript:

import React, { Component } from 'react';

interface TimerState {
  seconds: number;
}

class Timer extends Component<{}, TimerState> {
  private timerID: number | null = null; // Lưu ID của timer

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

  componentDidMount() {
    console.log('Timer componentDidMount: Starting timer...');
    // Bắt đầu timer
    this.timerID = window.setInterval(() => {
      this.tick();
    }, 1000);
  }

  componentWillUnmount() {
    console.log('Timer componentWillUnmount: Cleaning up timer...');
    // Dọn dẹp timer
    if (this.timerID !== null) {
      window.clearInterval(this.timerID);
    }
  }

  tick() {
    this.setState(prevState => ({
      seconds: prevState.seconds + 1
    }));
  }

  render() {
    return (
      <div>
        Seconds: {this.state.seconds}
      </div>
    );
  }
}

// Để test componentWillUnmount, bạn cần một component cha để mount/unmount Timer
// Ví dụ: Toggle nút ẩn/hiện Timer
// class App extends Component<{}, { showTimer: boolean }> {
//     state = { showTimer: true };
//     render() {
//         return (
//             <div>
//                 <button onClick={() => this.setState(prevState => ({ showTimer: !prevState.showTimer }))}>
//                     Toggle Timer
//                 </button>
//                 {this.state.showTimer && <Timer />}
//             </div>
//         );
//     }
// }

Giải thích: Trong componentDidMount, chúng ta thiết lập một setInterval và lưu ID của nó vào this.timerID. Trong componentWillUnmount, chúng ta sử dụng ID đã lưu để gọi clearInterval. Việc này đảm bảo rằng timer sẽ dừng lại và không tiếp tục chạy sau khi component không còn tồn tại trên màn hình nữa, tránh rò rỉ bộ nhớ.

Giai đoạn Xử lý lỗi (Error Handling)

Các phương thức này được gọi khi có lỗi xảy ra ở bất kỳ đâu trong cây component con trong quá trình render, trong lifecycle methods, hoặc trong constructors của bất kỳ component con nào.

  1. static getDerivedStateFromError(error)
  2. componentDidCatch(error, errorInfo)

Các phương thức này được sử dụng để tạo Error Boundaries (các component có khả năng "bắt" lỗi trong cây con của chúng và hiển thị giao diện dự phòng thay vì làm sập toàn bộ ứng dụng).

static getDerivedStateFromError(error)

Đây là một static method được gọi sau khi một lỗi được ném ra trong cây component con. Nó nhận đối tượng lỗi làm tham số.

Nó nên trả về một object để cập nhật state của component Error Boundary, cho phép component hiển thị giao diện người dùng dự phòng (fallback UI).

Ví dụ với TypeScript:

import React, { Component, ErrorInfo } from 'react';

interface ErrorBoundaryState {
  hasError: boolean;
}

class ErrorBoundary extends Component<{}, ErrorBoundaryState> {
  constructor(props: {}) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    // Cập nhật state để render UI dự phòng
    console.error("Error caught by getDerivedStateFromError:", error);
    return { hasError: true };
  }

  // ... componentDidCatch method
  // ... render method
}

Giải thích: Khi một lỗi xảy ra trong cây con, getDerivedStateFromError được gọi. Nó đặt hasError trong state thành true, signal cho component Error Boundary để render giao diện khác.

componentDidCatch(error, errorInfo)

Phương thức này được gọi sau khi một lỗi đã được "bắt" bởi getDerivedStateFromError. Nó nhận đối tượng lỗi và một object chứa thông tin về lỗi (errorInfo) làm tham số.

Nó thường được sử dụng để thực hiện các side effects như ghi log lỗi (logging) đến một dịch vụ bên ngoài (ví dụ: Sentry, Bugsnag).

Ví dụ với TypeScript:

import React, { Component, ErrorInfo } from 'react';

interface ErrorBoundaryState {
  hasError: boolean;
}

class ErrorBoundary extends Component<{}, ErrorBoundaryState> {
  constructor(props: {}) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Ví dụ: Ghi log lỗi đến dịch vụ báo cáo lỗi
    // logErrorToMyService(error, errorInfo);
    console.error("Error caught by componentDidCatch:", error, errorInfo);
    // Bạn CÓ THỂ gọi setState ở đây, nhưng nó đã được xử lý bởi getDerivedStateFromError
    // this.setState({ hasError: true }); // Thường không cần thiết nếu đã dùng getDerivedStateFromError
  }

  render() {
    if (this.state.hasError) {
      // Render UI dự phòng
      return <h1>Something went wrong.</h1>;
    }

    // Render children bình thường
    return this.props.children;
  }
}

// Cách sử dụng
// <ErrorBoundary>
//   <MyComponentThatMightThrowError />
// </ErrorBoundary>

Giải thích: componentDidCatch nhận lỗi và thông tin chi tiết. Đây là nơi bạn gửi thông tin lỗi này đến một hệ thống logging hoặc giám sát lỗi. Phương thức render của Error Boundary kiểm tra this.state.hasError để hiển thị UI dự phòng hoặc các component con bình thường.

Các Phương Thức Đã Bị Deprecated

Có một số lifecycle methods cũ đã bị đánh dấu là UNSAFE_ và nên tránh sử dụng trong code mới vì chúng có thể dẫn đến các vấn đề hoặc hành vi khó đoán trong các chế độ Strict Mode của React:

  • UNSAFE_componentWillMount()
  • UNSAFE_componentWillReceiveProps()
  • UNSAFE_componentWillUpdate()

Trong hầu hết các trường hợp, bạn có thể thay thế chúng bằng componentDidMount, componentDidUpdate, hoặc static getDerivedStateFromProps (dù cái cuối này cũng nên dùng cẩn thận).

Tóm tắt Chu Trình

Để hình dung lại, đây là thứ tự các methods được gọi trong mỗi giai đoạn:

  • Mounting: constructor -> static getDerivedStateFromProps -> render -> componentDidMount
  • Updating: static getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate
  • Unmounting: componentWillUnmount
  • Error Handling: static getDerivedStateFromError (khi render/methods/constructor ném lỗi) -> componentDidCatch (sau khi lỗi được bắt)

Việc nắm vững các lifecycle methods này giúp bạn hiểu sâu hơn cách React hoạt động "ngầm" và cho phép bạn kiểm soát chính xác khi nào component của bạn thực hiện các hành động cụ thể, từ đó xây dựng các ứng dụng mạnh mẽ và hiệu quả.

Hy vọng bài viết này đã cung cấp cho bạn cái nhìn rõ ràng và chi tiết về lifecycle methods trong React class components với TypeScript! Hãy thực hành viết các components sử dụng các methods này để củng cố kiến thức nhé.

Comments

There are no comments at the moment.