Higher Order Component (HOC) là một kỹ thụât nâng cao trong React với mục đích để tái sử dụng logic của các component. Vậy HOC là gì? Cách sử dụng HOC ra sao?
HOC là gì?
HOC – Higher Order Component không phải là một API của React, mà được xem là một pattern của React.
HOC thường được implement như một function nhận vào một component và trả về một component mới.
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Nếu như một component nhận vào props để xử lý và hiển thị thành UI thì một HOC nhận vào một component và chuyển đổi thành một component khác.
HOC được sử dụng rộng rãi trong các thư viện của React (Redux, Relay…)
Ưu điểm:
- Cho phép reuse code, logic của component.
- Bổ sung, thay đổi logic trước cho một component mới trước khi return (render hijacking)
- Điều khiển state và props.
Hướng dẫn sử dụng HOC
Trong React, các component là thành phần chính trong việc tái sử dụng. Chúng ta có thể tìm ra một vài logic tương tự với nhau trong các component, mặc dù chúng sử dụng các action, data khác nhau.
Ví du, một component UserList lấy dữ liệu từ state và render một danh sách user:
const users = [
{
id: "user1",
username: "Danny Le",
job: "Developer",
age: "27",
gender: "male",
level: "gold",
email: "danny.thanhtruc@gmail.com",
},
{
id: "user2",
username: "Vivian Le",
job: "Developer",
age: "27",
gender: "male",
level: "silver",
email: "vivian.le@gmail.com",
},
];
function User(props) {
const { data } = props;
return (
<>
<hr />
<p>
<b>User name:</b> {data.username}
</p>
<p>
<b>Job:</b> {data.job}
</p>
<p>
<b>Level:</b> {data.level}
</p>
<p>
<b>Contact:</b> {data.email}
</p>
</>
);
}
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
};
}
componentDidMount() {
this.setState({ users });
}
render() {
return (
<>
<h1>List User</h1>
{this.state.users.map((user) => (
<User data={user} />
))}
</>
);
}
}
Sau đó, chúng ta có một component ProductList với pattern tương tự:
const products = [
{
availableSizes: ["S", "XS"],
currencyFormat: "$",
currencyId: "USD",
description: "4 MSL",
id: 12,
installments: 9,
isFreeShipping: true,
price: 10.9,
sku: 12064273040195392,
style: "Black with custom print",
title: "Cat Tee Black T-Shirt",
},
{
availableSizes: ["M"],
currencyFormat: "$",
currencyId: "USD",
description: "",
id: 13,
installments: 5,
isFreeShipping: true,
price: 29.45,
sku: 51498472915966370,
style: "Front print and paisley print",
title: "Dark Thug Blue-Navy T-Shirt",
},
];
function Product(props) {
const { data } = props;
return (
<>
<hr />
<p>
<b>Title:</b> {data.title}
</p>
<p>
<b>Style:</b> {data.style}
</p>
<p>
<b>Price:</b> {data.price}
</p>
<p>
<b>Description:</b> {data.description}
</p>
<p>
<b>Free shipping:</b> {data.isFreeShipping}
</p>
</>
);
}
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = {
products: [],
};
}
componentDidMount() {
this.setState({ products });
}
render() {
return (
<>
<h1>List Product</h1>
{this.state.products.map((product) => (
<Product data={product} />
))}
</>
);
}
}
Hai component UserList và ProductList không giống nhau. Chúng nhận data khác nhau để setState và render nội dung khác nhau. Tuy nhiên,component UserList và ProductList có những logic chung với nhau như:
- Chúng đều khởi tạo một mảng data rỗng ở state.
- Sau khi render, chúng đều gọi đến hàm setState để cập nhật lại dữ liệu.
- Hàm render đều có title, duyệt mảng data của state để render các item theo dữ liệu tương ứng.
Trong một ứng dụng lớn, có thể có nhiều logic như trên được sử dụng lặp đi lặp lại. Chúng ta cần một nơi để gom những logic này lại để dùng chung cho các component khác. Lúc này, HOC được phát huy điểm mạnh của mình.
Chúng ta viết thêm một function để tạo ra các component có cùng logic như ví dụ về UserList và ProductList ở trên. Function này sẽ nhận vào một component và lấy data trả về như một props. Ta tạo một hàm tên là withSubscription nhận đầu vào là component con, data và tiêu đề như sau:
function withSubscription(WrappedComponent, dataSource, title) {
// ...trả về một component khác
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
};
}
componentDidMount() {
this.setState({ dataSource });
}
render() {
// ... và render component đầu vào với dữ liệu mới!
return (
<>
<h1>{title}</h1>
{this.state.dataSource.map((data) => (
<WrappedComponent data={data} />
))}
</>
);
}
};
}
Sau đó chúng ta chỉ cần gọi đến hàm withSubscription trên, truyền vào các argument cần thiết và render như một component bình thường như sau:
const UserList = withSubscription(User, users, "User List");
const ProductList = withSubscription(Product, products, "Product List");
function App() {
return (
<div className="App">
<UserList />
<ProductList />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
HOC không chỉnh sửa hoặc làm thay đổi component đầu vào WrappedComponent mà chỉ kế thừa các hành vi của component này.
HOC ‘composes‘ component đầu vào bằng cách ‘wrapping‘ nó trong một component.
HOC là một pure component (không có side effect).
Component bên trong có thể nhận tất cả các props (các argument của HOC: data, title…) để sử dụng cho việc render. HOC không quan tâm dữ liệu sẽ được component sử dụng như thế nào, ngược lại component con cũng sẽ không quan tâm props được nhận từ đâu.
HOC chỉ là một hàm bình thường, ta có thể thêm vào bất kì tham số nào, số lượng tùy ý.
Mỗi quan hệ giữa HOC và component con cũng dựa vào props (agument) như các component bình thường, miễn là data của HOC cung cấp đủ để đáp ứng với logic của component con.
Các component được sinh ra từ một HOC vẫn hiển thị trên React Dev Tool. Vì thế, chúng ta có thể dễ dàng debug:
Như vậy, trong bài viết này, chúng ta đã tìm hiểu về khái niệm HOC cũng như cách sử dụng. Đến bài viết sau, chúng ta sẽ tìm hiểu xem những quy ước và lưu ý khi sử dụng Higher Order Component nhé. Cám ơn các bạn đã ghé thăm bài viết!
Bài viết có tham khảo thông tin từ link: https://reactjs.org/docs/higher-order-components.html
Xem bài viết sau tại: ReactJS: Higher Order Components – HOC (Phần 2)