Phần 2 - React JS là gì? và 5 Best Practices về kiến trúc khi làm việc với React JS
3. Higher-order Component (HOC)
Best practice thứ 3 mà Bizfly Cloud giới thiệu tiếp theo trong 5 Best Practices về kiến trúc khi làm việc với React JS là Higher-order Component (HOC). Bạn có thể kiểm tra để đảm bảo rằng một React JS Component nào đó sẽ chỉ xuất hiện khi mà người dùng đã đăng nhập vào ứng dụng bằng cách dùng hàm Render liên tục. Tuy nhiên điều này sẽ khiến bạn nhận ra rằng mình đang lặp code rất nhiều.
Bạn có biết các higher-order component có thể giúp cho bạn tách và trừu tượng hóa một số tiêu chí của component. Higher-order component giống như một dạng của decorator pattern theo thuật ngữ của các nhà phát triển phần mềm. Nó chỉ là 1 hàm nhận 1 React JS component làm tham số và trả về một React JS component khác. Xem ví dụ bên dưới.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
export default function requiresAuth(WrappedComponent) {
class AuthenticatedComponent extends Component {
static propTypes = {
user: PropTypes.object,
dispatch: PropTypes.func.isRequired
};
componentDidMount() {
this._checkAndRedirect();
}
componentDidUpdate() {
this._checkAndRedirect();
}
_checkAndRedirect() {
const { dispatch, user } = this.props;
if (!user) {
dispatch(push('/signin'));
}
}
render() {
return (
<div className="authenticated">
{ this.props.user ? <WrappedComponent {...this.props} /> : null }
</div>
);
}
}
const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
AuthenticatedComponent.displayName = `Authenticated(${wrappedComponentName})`;
const mapStateToProps = (state) => {
return {
user: state.account.user
};
};
return connect(mapStateToProps)(AuthenticatedComponent);
}
Thông qua ví dụ trên, có thể thấy rằng hàm requiresAuth nhận vào một component (WrappedComponent) dưới dạng một tham số và component đó sẽ được "trang trí" những tính năng mà nhà phát triển mong muốn.
Class AuthenticatedComponent sẽ render ra component đó và thêm vào các tính năng bên trong hàm nhằm mục đích kiểm tra xem liệu người dùng có tồn tại hay không. Nếu người dùng không tồn tại thì nó sẽ tự động redirect sang trang đăng nhập. Component này sẽ được kết nối đến store của Redux và return về sẽ là bước sau cùng. Lưu ý là Redux trong ví dụ này thì có ích nhưng không nhất thiết lúc nào cũng dùng tới nó nhé.
4. Truyền hàm render vào component với children prop
Best practice thứ 4 trong 5 Best Practices về kiến trúc khi làm việc với React JS là truyền hàm render vào component với children prop. Bạn có biết tạo ra 1 Row của Table có thể collapse là 1 nhiệm vụ hết sức khó khăn. Và nếu có thì bạn sẽ phải render nó như thế nào? Nếu table không collapse thì bạn sẽ phải hiển thị children như thế nào? Mọi thứ sẽ dễ dàng hơn với JSX 2.0 vì bạn có thể return một mảng thay vì duy nhất một html tag. Hãy xem table dưới đây để hiểu thêm về pattern Function as children nhé.
export default class Table extends Component {
render() {
return (
<table>
<thead>
<tr>
<th>Just a table</th>
</tr>
</thead>
{this.props.children}
</table>
);
}
}
Và một table body có thể collapse như sau
import React, { Component } from "react";
export default class CollapsibleTableBody extends Component {
constructor(props) {
super(props);
this.state = { collapsed: false };
}
toggleCollapse = () => {
this.setState({ collapsed: !this.state.collapsed });
};
render() {
return (
<tbody>
{this.props.children(this.state.collapsed, this.toggleCollapse)}
</tbody>
);
}
}
You’d use this component in the following way:
<Table>
<CollapsibleTableBody>
{(collapsed, toggleCollapse) => {
if (collapsed) {
return (
<tr>
<td>
<button>
</td>
</tr>
);
} else {
return (
<tr>
<td>
<button>
</td>
<td>CollapsedContent</td>
</tr>
);
}
}}
</CollapsibleTableBody>
</Table>
Đơn giản bạn chỉ cần chuyển 1 function qua children. Function sẽ được gọi trong các hàm render của các component. Người ta hay gọi kỹ thuật này là render callback hay trong 1 số trường hợp sẽ được gọi là render prop. Thuật ngữ này sẽ được giải thích trong phần cuối cùng của 5 Best Practices về kiến trúc khi làm việc với React JS bên dưới.
5. Render props
Có lẽ trong 5 Best Practices về kiến trúc khi làm việc với React JS, render props còn hơi xa lạ với các lập trình viên mới vào nghề. Tuy nhiên đây là thuật ngữ không thể không nhắc đến khi làm việc với React JS. Thuật ngữ này ý muốn nói đến việc pattern higher-order component lúc nào cũng có thể được thay thế bởi các component bình thường với một "render prop".
Ý tưởng đầu tiên là việc truyền 1 React JS Component vào 1 hàm để có thể gọi dưới dạng 1 thuộc tính và sau đó gọi hàm này trong hàm render. Tham khảo đoạn code dưới đây:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Fetch extends Component {
static propTypes = {
render: PropTypes.func.isRequired,
url: PropTypes.string.isRequired,
};
state = {
data: {},
isLoading: false,
};
_fetch = async () => {
const res = await fetch(this.props.url);
const json = await res.json();
this.setState({
data: json,
isLoading: false,
});
}
componentDidMount() {
this.setState({ isLoading: true }, this._fetch);
}
render() {
return this.props.render(this.state);
}
}
Đến đây bạn có thể thấy có 1 function được gọi trong quá trình render như 1 thuộc tính render. Hàm này được gọi trong nó và lấy về state hoàn chỉnh để làm tham số và trả về JSX. Xem công năng của nó như sau:
<Fetch
url="https://api.github.com/users/imgly/repos"
render={({ data, isLoading }) => (
<div>
<h2>img.ly repos</h2>
{isLoading && <h2>Loading...</h2>}
<ul>
{data.length > 0 && data.map(repo => (
<li key={repo.id}>
{repo.full_name}
</li>
))}
</ul>
</div>
)} />
Với đoạn code trên, tham số data và isLoading được destruct từ phía object state và được dùng để lấy về response của JSX. Một dòng “Loading” sẽ được hiển thị trong lúc promise chưa được fulfilled. Đến đây bạn có thể quyết định phần nào của state sẽ được truyền vào cho “render prop” cũng như cách bạn dùng chúng cho giao diện của mình. Không gì có thể ngăn cản bạn có nhiều render props trong 1 component vì render prop pattern là sự tổng quát của pattern Function as children. Thật thú vị phải không nào?
Trên đây là tập hợp 5 Best Practices về kiến trúc khi làm việc với React JS phổ biến nhất và mang lại hiệu quả cao nhất cho các nhà phát triển phần mềm. Tổng hợp 5 Best Practices về kiến trúc khi làm việc với React JS này được rút ra từ những kinh nghiệm quý báu của những người đi tiên phong trong việc áp dụng React JS, thư viện thông minh nhất ở Javascript để xây dựng giao diện người dùng. Rất hy vọng bài viết này sẽ mang lại cho bạn những kiến thức bổ ích. Cảm ơn và chúc bạn thành công.