Facebook Pixel

So sánh giữa HOCs, Render Props và Hooks.

05 Dec, 2024

Tran Thuy Vy

Frontend Developer

So sánh ba phương pháp tái sử dụng logic giữa các component: Higher-Order Components (HOCs), Render Props, và Hooks

So sánh giữa HOCs, Render Props và Hooks.

Mục Lục

Chủ đề hôm nay mình muốn chia sẻ với các bạn về ba pattern phổ biến trong React: Higher-Order Components (HOCs), Render Props, và Hooks. Đây đều là những công cụ giúp bạn tái sử dụng logic giữa các component. Hãy cùng tìm hiểu từng phương pháp, so sánh chúng, và xem xét khi nào nên sử dụng phương pháp nào. Mình cũng sẽ cung cấp các ví dụ và đoạn code cụ thể để minh họa sự khác nhau giữa chúng.

1. So sánh tổng quan

Higher-Order Components (HOCs) Render Props Hooks
Cơ chế triển khai Hàm nhận component và trả về component mới Component nhận prop là một hàm render Sử dụng trong functional components với hooks
Tái sử dụng logic Bằng cách bọc component Bằng cách sử dụng hàm render Bằng cách tạo custom hooks
Độ phức tạp code Có thể cao khi lồng nhiều HOC Có thể gây lồng nhau trong JSX Thường ngắn gọn và dễ đọc
Xung đột tên Có thể xảy ra, cần cẩn thận Tránh được xung đột tên Tránh được xung đột tên
Khả năng debug Khó hơn do nhiều lớp bọc Trung bình, có thể khó với nhiều render props Dễ hơn, stack trace rõ ràng
Hỗ trợ Hoạt động với cả class và functional components Hoạt động với cả class và functional components Chỉ hoạt động với functional components
Hiệu suất Có thể ảnh hưởng nếu không tối ưu Có thể ảnh hưởng do tạo hàm mới mỗi lần render Tối ưu hơn, hooks được React tối ưu
Độ phổ biến hiện tại Ít phổ biến hơn do sự xuất hiện của hooks Vẫn được sử dụng trong một số trường hợp Rất phổ biến và được khuyến nghị

2. Cơ chế hoạt động

Trong phần này, mình sẽ mô tả cơ chế hoạt động của từng phương pháp, kèm theo mô tả về sơ đồ minh họa để giúp bạn hình dung rõ hơn.

2.1 HOCs

HOC là hàm nhận vào component và trả về một component mới, cho phép bạn thêm hoặc thay đổi hành vi của component gốc mà không cần sửa đổi nó.

Cơ chế hoạt động của HOC
  • Original Component (ComponentA): component gốc mà bạn muốn mở rộng hoặc thêm logic mới.
  • HOC Function (withExtraLogic): HOC nhận vào ComponentA làm tham số.
  • Enhanced Component (ComponentB): kết quả của HOC sau khi áp dụng logic bổ sung lên ComponentA.
  • Rendered Output: giao diện được render ra sau khi sử dụng ComponentB.

2.2 Render props

Sử dụng prop có giá trị là hàm để kiểm soát những gì được render.

Cơ chế hoạt động render props
  • Component cha (DataProvider): chứa logic chung và gọi hàm render prop.
  • Hàm render prop (children): hàm được truyền vào DataProvider để quyết định render gì dựa trên dữ liệu nhận được.
  • Component con (ChildComponent): được render thông qua hàm render prop, sử dụng dữ liệu từ DataProvider.

2.3 Hook

Sử dụng các hàm hook để thêm state và các tính năng React khác vào functional components.

Cơ chế hoạt động Hook
  • Functional Component (MyComponent): Dùng hooks để quản lý state và side effects.
  • Custom Hook (useCustomLogic): chứa logic chung, có thể được tái sử dụng.
  • State và Side Effects: được quản lý trong MyComponent thông qua hooks.

3. Ví dụ và code

Mình sẽ lấy ví dụ: get data từ API và hiển thị trong component.

3.1 Triển khai với HOCs (Higher-Order Components)

JSX
import React from 'react';

function withDataFetching(WrappedComponent, dataSource) {
  return class extends React.Component {
    state = {
      data: [],
      isLoading: false,
      error: null,
    };

    componentDidMount() {
      this.setState({ isLoading: true });
      fetch(dataSource)
        .then(response => response.json())
        .then(data => this.setState({ data, isLoading: false }))
        .catch(error => this.setState({ error, isLoading: false }));
    }

    render() {
      return (
        <WrappedComponent
          {...this.props}
          data={this.state.data}
          isLoading={this.state.isLoading}
          error={this.state.error}
        />
      );
    }
  };
}

export default withDataFetching;
  • Sử dụng trong UserList.js
JSX
import React from 'react';
import withDataFetching from './withDataFetching';

function UserList({ data, isLoading, error }) {
  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error fetching data!</p>;
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default withDataFetching(UserList, 'https://api.example.com/users');

withDataFetching bọc UserList, thêm logic lấy dữ liệu và truyền dữ liệu thông qua props.

Bạn có thể thấy được những điểm hạn chế khi sử dụng HOCs:

  • Có thể xảy ra xung đột tên với các props khác.
  • Code có thể khó debug khi bạn lồng nhiều HOC.
  • Không linh hoạt trong việc kiểm soát render.

3.2 Triển khai với Render Props

JSX
import React from 'react';

class DataFetcher extends React.Component {
  state = {
    data: [],
    isLoading: false,
    error: null,
  };

  componentDidMount() {
    const { url } = this.props;
    this.setState({ isLoading: true });
    fetch(url)
      .then(response => response.json())
      .then(data => this.setState({ data, isLoading: false }))
      .catch(error => this.setState({ error, isLoading: false }));
  }

  render() {
    return this.props.children({
      data: this.state.data,
      isLoading: this.state.isLoading,
      error: this.state.error,
    });
  }
}

export default DataFetcher;
  • Sử dụng Render Props trong UserList.js
JSX
import React from 'react';
import DataFetcher from './DataFetcher';

function UserList() {
  return (
    <DataFetcher url="https://api.example.com/users">
      {({ data, isLoading, error }) => {
        if (isLoading) return <p>Loading...</p>;
        if (error) return <p>Error fetching data!</p>;
        return (
          <ul>
            {data.map(user => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
        );
      }}
    </DataFetcher>
  );
}

export default UserList;
  • DataFetcher chứa logic để lấy dữ liệu và gọi hàm render prop với dữ liệu đó.
  • UserList sử dụng render prop để quyết định render gì dựa trên dữ liệu nhận được.

Những điểm hạn chế khi sử dụng:

  • Sử dụng sẽ dẫn đến việc lồng nhau trong JSX, làm code trở nên khó đọc.
  • Mỗi lần render tạo ra một function mới, có thể ảnh hưởng đến hiệu suất.

3.3 Triển khai với Hooks

JSX
import { useState, useEffect } from 'react';

function useDataFetching(dataSource) {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    fetch(dataSource)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setIsLoading(false);
      })
      .catch(error => {
        setError(error);
        setIsLoading(false);
      });
  }, [dataSource]);

  return { data, isLoading, error };
}

export default useDataFetching;
  • Sử dụng Hooks trong UserList.js
JSX
import React from 'react';
import useDataFetching from './useDataFetching';

function UserList() {
  const { data, isLoading, error } = useDataFetching('https://api.example.com/users');

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error fetching data!</p>;
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;
  • useDataFetching là custom hook chứa logic để lấy dữ liệu.
  • UserList gọi hook này và nhận về dữ liệu, sau đó render trực tiếp.

Những điểm hạn chế của hooks:

  • Không thể sử dụng hooks trong class components.
  • Phải tuân theo các quy tắc như không gọi hooks trong vòng lặp, điều kiện, hoặc hàm lồng nhau.

Từ những phần trên mình có thể đưa ra kết luận như thế này:

  1. Hooks giải quyết hạn chế:
  • Tránh xung đột tên: hooks không cần bọc component hoặc truyền props
  • Code ngắn gọn hơn: không có lồng nhau trong JSX như Render Props.
  • Dễ debug hơn: không có nhiều lớp bọc như HOCs.
  1. Render Props giải quyết hạn chế:
  • Tránh xung đột tên: do không truyền thêm props vào component con.
  • Kiểm soát linh hoạt cách render: Component con quyết định render gì dựa trên dữ liệu nhận được.
  1. HOCs giải quyết hạn chế:
  • Tránh lồng nhau trong JSX: HOCs không làm tăng mức độ lồng nhau như Render Props.
  • Có thể sử dụng với class components: HOCs hoạt động với cả class và functional components, trong khi Hooks chỉ hoạt động với functional components.

4. Khi nào nên sử dụng phương pháp nào?

4.1 Sử dụng Hooks khi

  • Bạn đang sử dụng React phiên bản 16.8 trở lên.
  • Bạn làm việc với functional components.
  • Bạn muốn code ngắn gọn, dễ đọc và dễ bảo trì.
  • Bạn muốn tránh xung đột tên và lồng nhau trong JSX.

4.2 Sử dụng Render Props khi

  • Bạn cần kiểm soát linh hoạt cách render của component con.
  • Bạn làm việc với class components hoặc cần tương thích với code cũ.
  • Bạn muốn tránh xung đột tên mà HOCs có thể gây ra.

4.4 Sử dụng HOCs khi

  • Bạn làm việc với class components và không thể sử dụng Hooks.
  • Bạn cần tái sử dụng logic chung mà không muốn thay đổi cấu trúc component hiện có.
  • Bạn muốn tránh lồng nhau trong JSX mà Render Props có thể gây ra.

5. Kết luận

Trong React, việc lựa chọn giữa Higher-Order Components, Render Props, và Hooks phụ thuộc vào hoàn cảnh cụ thể của dự án và nhu cầu của bạn. Mỗi phương pháp đều có ưu điểm và hạn chế riêng.

Hy vọng bài viết này đã giúp bạn hiểu rõ hơn về sự khác nhau giữa HOCs, Render Props và Hooks, cũng như cách áp dụng chúng trong dự án của mình.

Các bài viết liên quan:

Bài viết liên quan

Lập trình backend expressjs

xây dựng hệ thống microservices
  • Kiến trúc Hexagonal và ứng dụngal font-
  • TypeScript: OOP và nguyên lý SOLIDal font-
  • Event-Driven Architecture, Queue & PubSubal font-
  • Basic scalable System Designal font-

Đăng ký nhận thông báo

Đừng bỏ lỡ những bài viết thú vị từ 200Lab