Hôm nay, mình muốn chia sẻ với các bạn về khái niệm rất thú vị trong React mà mình đã học, đó là Higher-Order Components (HOCs). Nếu bạn đang tìm cách để tái sử dụng logic giữa các component cách hiệu quả, thì HOCs chính là công cụ đắc lực dành cho bạn.
1. HOCs (Higher-Order Components) là gì?
HOCs (Higher-Order Components) là một kỹ thuật nâng cao trong React để tái sử dụng logic của component. HOC không phải là một phần của API React, chúng là một pattern xuất phát từ tính chất kết hợp của React.
Trước tiên, hãy cùng mình tìm hiểu xem HOCs là gì nhé. Hiểu cách đơn giản, Higher-Order Component là hàm nhận vào component và trả về component mới với các tính năng hoặc logic bổ sung. Nói cách khác, HOCs là cách để chúng ta "nâng cấp" component bằng cách bọc nó trong một HOCs.
Ví dụ: Giả sử bạn có nhiều component cần truy cập vào dữ liệu từ một API. Thay vì viết logic gọi API trong mỗi component, bạn có thể tạo một HOC để xử lý việc này.
// HOC get data from API
function withDataFetching(WrappedComponent, dataSource) {
return class extends React.Component {
state = {
data: [],
isLoading: true,
error: null,
};
componentDidMount() {
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}
/>
);
}
};
}
function UserList(props) {
const { data, isLoading, error } = props;
if (isLoading) return <div>Đang tải...</div>;
if (error) return <div>Lỗi: {error.message}</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Sử dụng HOC để tạo ra một component mới với dữ liệu
const UserListWithData = withDataFetching(
UserList,
'https://api.example.com/users'
);
withDataFetching
là HOC chịu trách nhiệm lấy dữ liệu từ API.UserList
là component gốc hiển thị danh sách người dùng.UserListWithData
là component mới được tạo ra từ HOC, có khả năng tự động lấy dữ liệu và cung cấp cho UserList.
2. Tại sao cần dùng HOCs?
Trong quá trình phát triển ứng dụng, chắc hẳn bạn đã từng gặp trường hợp cần áp dụng cùng một logic cho nhiều component khác nhau. Việc lặp đi lặp lại cùng một đoạn code trong nhiều nơi không chỉ gây rối mắt mà còn khó bảo trì.
- HOCs giúp chúng ta tái sử dụng logic, giảm thiểu việc lặp lại code và nguy cơ tạo ra lỗi không mong muốn.
- Bằng cách tách biệt logic chung ra khỏi component cụ thể, code của chúng ta trở nên rõ ràng và dễ hiểu hơn.
- Bạn có thể kết hợp nhiều HOCs để tạo ra các component với tính năng phong phú.
Ví dụ: giả sử mình muốn tạo ra một số văn bản với frontSize lớn và bold. Thay vì thêm thuộc tính style cho từng component, mình có thể tạo ra hai HOCs như sau:
- withLargeFontSize: thêm thuộc tính fontSize: "90px" vào style.
- withBoldFontWeight: thêm thuộc tính fontWeight: "bold" vào style.
Bằng cách bọc component của mình với các HOCs này, mình có thể dễ dàng áp dụng các style mong muốn mà không cần phải lặp lại code.
3. Cách triển khai HOCs pattern
Để tạo một HOCs, bạn thực hiện theo các bước sau:
- Nhận một component làm tham số: HOCs là một hàm nhận vào một component.
- Áp dụng logic bổ sung: trong HOCs, bạn hãy thêm logic hoặc dữ liệu mà bạn muốn tái sử dụng.
- Trả về component mới: HOCs trả về component mới với các tính năng đã được mở rộng.
Đây là một ví dụ về HOCs withStyles:
export function withStyles(Component) {
return (props) => {
const style = {
color: "red",
fontSize: "1em",
...props.style,
};
return <Component {...props} style={style} />;
};
}
Trong ví dụ này, withStyles nhận vào một component và trả về một hàm mới nhận props. Mình tạo thêm một đối tượng style mới, kết hợp các style mới và các style từ props để tránh ghi đè. Cuối cùng, mình trả về Component gốc với các props và style mới.
Sau khi tạo HOCs, mình sẽ sử dụng nó như sau:
import { withStyles } from "./hoc/withStyles";
const Text = () => <p style={{ fontFamily: "Inter" }}>Hello world!</p>;
const StyledText = withStyles(Text);
Hoặc nếu như bạn muốn component của bạn luôn được bao bọc bởi HOCs thì có thể sử dụng như thế này:
const Text = withStyles(() => (
<p style={{ fontFamily: "Inter" }}>Hello world!</p>
));
Đây là một ví dụ mình thường sử dụng HOCs pattern: giả sử bạn có nhiều component cần truy cập vào dữ liệu từ API. Bạn có thể tạo một HOCs để xử lý việc lấy dữ liệu và truyền nó vào component:
function withDataFetching(Component, 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 <Component {...this.props} {...this.state} />;
}
};
}
Sau đó, bạn có thể sử dụng HOCs này cho bất kỳ component nào cần sử dụng dữ liệu đó:
const UserList = (props) => {
const { data, isLoading, error } = props;
// Hiển thị danh sách người dùng hoặc loading
};
export default withDataFetching(UserList, "https://api.example.com/users");
4. Những điểm cần lưu ý khi sử dụng HOCs
4.1 Xung đột tên (Naming Collisions)
Một vấn đề có thể gặp phải khi sử dụng HOCs là xung đột tên giữa các props của HOCs và component gốc. Ví dụ, nếu HOCs của bạn thêm một prop style, nó có thể ghi đè lên prop style của component gốc. Để tránh điều này, bạn cần cẩn thận khi kết hợp các props.
function withStyles(Component) {
return (props) => {
const style = {
padding: "0.2rem",
margin: "1rem",
...props.style,
};
return <Component {...props} style={style} />;
};
}
const Button = () => <button style={{ color: "red" }}>Click me!</button>;
const StyledButton = withStyles(Button);
Trong ví dụ trên, mình sử dụng ...props.style
để kết hợp các style từ props, đảm bảo rằng chúng không bị ghi đè bởi HOCs.
4.2 Đọc hiểu code (Readability)
Khi sử dụng nhiều HOCs lồng nhau, code của bạn có thể trở nên khó đọc và khó debug. Hãy tưởng tượng một component được bọc bởi ba hoặc bốn HOCs, việc xác định HOCs nào chịu trách nhiệm cho phần nào của code có thể trở nên phức tạp. Để giải quyết vấn đề này, chúng ta có thể:
- Đặt tên HOCs và các biến một cách rõ ràng để có thể dễ dàng theo dõi.
- Các thư viện như recompose có thể giúp quản lý HOCs một cách hiệu quả.
- Cân nhắc sử dụng Hooks hoặc Render Props: trong một số trường hợp, Hooks hoặc Render Props có thể là giải pháp thay thế tốt hơn.
5. Khi nào nên sử dụng HOCs?
- Khi bạn cần thêm logic chung cho nhiều component khác nhau.
- Khi logic không phù hợp để đặt trong component cơ bản.
- Khi bạn muốn giữ cho component cơ bản càng đơn giản càng tốt.
- Khi bạn cần tái sử dụng logic mà không muốn thay đổi cấu trúc component hiện có.
6. Kết luận
Qua bài viết này, mình hy vọng các bạn đã hiểu rõ hơn về Higher-Order Components (HOCs) trong React. HOCs là một công cụ mạnh mẽ giúp bạn tái sử dụng logic, giữ cho code sạch sẽ và dễ bảo trì. Tuy nhiên, cũng cần lưu ý đến các vấn đề như xung đột props và tính phức tạp khi sử dụng nhiều HOCs lồng nhau.
Nếu bạn đang tìm kiếm cách để tối ưu hóa code của mình và tái sử dụng logic một cách hiệu quả, hãy thử áp dụng HOCs vào dự án của bạn. Đừng quên cân nhắc và lựa chọn phương pháp phù hợp nhất với nhu cầu cụ thể của bạn.
Các bài viết liên quan:
Bài viết liên quan
Render Props pattern là gì? Hướng dẫn sử dụng Render Props
Dec 03, 2024 • 8 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
Promise là gì? Hướng dẫn sử dụng Promise trong dự án React
Nov 27, 2024 • 7 min read
Async/await là gì? Hướng dẫn sử dụng Async/await trong dự án React
Nov 26, 2024 • 8 min read
Một số Phương pháp cải thiện Performance trong dự án React
Nov 25, 2024 • 16 min read
Hướng dẫn tích hợp Redux và React Query trong dự án React Vite
Nov 22, 2024 • 8 min read