- vừa được xem lúc

Higher-Order Components in React

0 0 32

Người đăng: Bach Van Ngoc

Theo Viblo Asia

Higher-Order Components

Higher-order component (HOC) là một kỹ thuật nâng cao trong React được sử dụng trong việc sử dụng lại các component. HOCs không là một phần trong React API. Một cách cụ thể, một higher-order component là một hàm và nó nhận đối số là một component và trả về một component mới. Higher-order component là một pattern tuyệt với và nó được chứng minh trong nhiều thư viện React ví dụ như Redux (thư viện được sử dụng để quản lý state) hay React-DnD (là một thư viện về drag and drop, nó khá khó hiểu cho người mới bắt đầu)...

const EnhancedComponent = higherOrderComponent(WrappedComponent);

Trong bài viết này, chúng ta sẽ thảo luận tại sao higher-order components thì thực sự hữu ích và cách để sử dụng nó.

Sử dụng HOCs cho Cross-Cutting Concerns

Cross-cutting concerns, hiểu nôm na là những xử lý phụ được xảy ra trước khi xử lý chính được thực hiện. Thực ra ta có thể sử dụng mixins để có thể áp dụng cross-cutting concerns, nhưng mixins sẽ tạo ra nhiều vấn đề hơn là những gì nó làm được.

Components là đơn vị cơ bản nhất trong React mà ta có thể sử dụng lại. Tuy nhiên, ta sẽ tìm một số các phần mà không phù hợp với việc ta đang muốn sử dụng và ta sẽ phải implement component đó theo mong muốn của ta.

Ví dụ, ta có component CommentList nhận thông tin từ một data source để render ra một danh sách các comments:

class CommentList extends React.Component { constructor() { super(); this.state = { // "DataSource" is some global data source comments: DataSource.getComments() }; } componentDidMount() { // Subscribe to changes DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { // Clean up listener DataSource.removeChangeListener(this.handleChange); } handleChange = () => { // Update component state whenever the data source changes this.setState({ comments: DataSource.getComments() }); } render() { return ( <div> {this.state.comments.map((comment) => ( <Comment comment={comment} key={comment.id} /> ))} </div> ); }
}

Sau đó, ta lại viết một component sử dụng cho việc nhận dữ liệu là các bài viết để hiện thị danh sách, với một pattern tương tự:

class BlogPost extends React.Component { constructor(props) { super(props); this.state = { blogPost: DataSource.getBlogPost(props.id) }; } componentDidMount() { DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange = () => { this.setState({ blogPost: DataSource.getBlogPost(this.props.id) }); } render() { return <TextBlock text={this.state.blogPost} />; }
}

Sau khi đọc xong 2 component trên, ta có thể thấy rằng 2 component trên đang thực hiện một quy trình tương tự:

  • Ở componentDidMount, truyền hàm khi thực hiện thay đổi handleChange cho DataSource.
  • Trong hàm handleChange thực hiện việc setState khi dữ liệu thay đổi.
  • Ở componentWillUnmount, thực hiện bỏ hàm handleChange.

Ta có thể tưởng tượng với một ứng dụng lớn, việc lặp lại này được thực hiện liên tục. Do vậy ta sẽ muốn một cách trừu tượng hóa để cho phép ta có thể định nghĩa logic ở một nơi và chia sẻ chúng cho nhiều compoment khác. Và chính điều này làm cho higher-order components nổi trội hơn cả.

Chúng ta có thể viết một hàm mà tạo ra các component như là CommentListBlogPost nhận thông tin từ DataSource. Nó sẽ nhận đối số là một component con:

const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments()
); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id)
});

Hàm này nhận 2 đối số, một là component được gói vào, hai là hàm lấy dữ liệu mà ta muốn.

Khi CommentListWithSubscriptionBlogPostWithSubscription được render thì CommentListBlogPost sẽ nhận dữ liệu từ prop:

function withSubscription(WrappedComponent, selectData) { return class extends React.Component { constructor(props) { super(props); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange = () => { this.setState({ data: selectData(DataSource, this.props) }); } render() { return <WrappedComponent data={this.state.data} {...this.props} />; } };
}

Note that an HOC doesn't modify the input component, nor does it use inheritance to copy its behavior. Rather, an HOC composes the original component by wrapping it in a container component. An HOC is a pure function with zero side-effects.

And that's it! The wrapped component receives all the props of the container, along with a new prop, data, which it uses to render its output. The HOC isn't concerned with how or why the data is used, and the wrapped component isn't concerned with where the data came from.

Cần nhớ một điều là HOC không chỉnh sửa, làm khác đi component đầu vào mà nó chỉ kế thừa các hành vi của component đó.

Component được gói vào nhận tất các props của container, với props nó có thể sử dụng để render ra nội dung mà mình mong muốn. HOC không liên quan đến việc dữ liệu được sử dụng như thế nào, từ đâu, và component được gói vào cũng không liên quan đến dữ liệu được truyền từ đâu.

Đừng thay đổi component gốc, hãy soạn lại component

Ví dụ ta có 1 HOC chỉ in ra props và trả về chính component đầu vào:

function logProps(InputComponent) { InputComponent.prototype.componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } return InputComponent;
} const EnhancedComponent = logProps(InputComponent);

Có một số vấn đề ở đây. Thứ nhất InputComponent không thể sử dụng lại một cách riêng biệt với EnhancedComponent. Quan trọng hơn nữa, nếu ta muốn sử dụng một HOC khác có EnhancedComponent thì cũng sẽ thay đổi hàm componentWillReceiveProps của HOC đầu tiên, vì vậy ta cần phải biết ta đang implement cái gì để tránh conflict với những HOC khác.

Thay vì thay đổi, ta nên sử dụng composition bằng việc gói component đầu vào bên trong một container:

function logProps(WrappedComponent) { return class extends React.Component { componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } render() { return <WrappedComponent {...this.props} />; } }
}

HOC này có chức năng tương tự như HOC ở bên trên (đã bị thay đổi) trong khi tránh được việc thay đổi, nguy cơ tiềm ẩn. This HOC has the same functionality as the mutating version while avoiding the potential for clashes. It works equally well with class and functional components. And because it's a pure function, it's composable with other HOCs, or even with itself.

You may have noticed similarities between HOCs and a pattern called container components. Container components are part of a strategy of separating responsibility between high-level and low-level concerns. Containers manage things like subscriptions and state, and pass props to components that handle things like rendering UI. HOCs use containers as part of their implementation. You can think of HOCs as parameterized container component definitions.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Cùng tìm hiểu về các hook trong React hooks

Đối với ai đã từng làm việc với React thì chắc hẳn đã có những lúc cảm thấy bối rối không biết nên dùng stateless (functional) component hay là stateful component. Nếu có dùng stateful component thì cũng sẽ phải loay hoay với đống LifeCycle 1 cách khổ sở Rất may là những nhà phát triển React đã kịp

0 0 82

- vừa được xem lúc

Khi nào nên (và không nên) sử dụng Redux

. Công việc quản lý state với những hệ thống lớn và phức tạp là một điều khá khó khăn cho đến khi Redux xuất hiện. Lấy cảm hứng từ design pattern Flux, Redux được thiết kế để quản lý state trong các project JavaScript.

0 0 106

- vừa được xem lúc

ReactJS: Props và State

Nếu bạn đã học ReactJS hay React Native, bạn sẽ thấy các Props và State được sử dụng rất nhiều. Vậy chính xác chúng là gì? Làm thế nào để chúng ta sử dụng chúng đúng mục đích đây.

0 0 42

- vừa được xem lúc

State và Props trong Reactjs

Hello các bạn, tiếp tục seri tìm hiểu về ReactJs hôm nay mình xin giới thiệu đến các bạn hai thứ mình cho là thú vị nhất của ReactJs là State và Props. State bạn có thể hiểu đơn giản là một nơi mà bạn lưu trữ dữ liệu của Component, từ đó bạn có thể luân chuyển dữ liệu đến các thành phần trong Compon

0 0 36

- vừa được xem lúc

Memoization trong React

. 1.Introduction. Memoization có liên quan mật thiết đến bộ nhớ đệm, và dưới đây là một ví dụ đơn giản:. const cache = {}.

0 0 38

- vừa được xem lúc

Nâng cao hiệu suất React Hooks với React.memo, Memoization và Callback Functions

1.Ngăn Re-render và React.memo. React.

0 0 68