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ó.
- 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.
- 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.
- 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)
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
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
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
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
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
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:
- 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.
- 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.
- 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
Server-Side Rendering: Giải thích cơ chế hoạt động của SSR
Dec 11, 2024 • 15 min read
Client-Side Rendering: Giải thích cơ chế hoạt động của CSR
Dec 09, 2024 • 8 min read
Vercel là gì? Hướng dẫn deploy dự án Next.js bằng Vercel
Dec 07, 2024 • 14 min read
Render Props pattern là gì? Hướng dẫn sử dụng Render Props
Dec 03, 2024 • 8 min read
HOCs Pattern là gì? Hướng dẫn triển khai Hocs Pattern trong dự án React
Dec 02, 2024 • 7 min read
Hooks Pattern là gì? Hướng dẫn áp dụng Hooks Pattern trong dự án React
Nov 28, 2024 • 11 min read