React Context API và các Higher-order Components
Biên dịch: Nguyễn Quốc Đại Có thể bạn đã gặp qua React’s Context API mà không nhận ra nó, nó tương tự với High-Order-Components. Nếu bạn đã từng làm việc với Redux, bạn sẽ có kinh nghiệm cả hai, React’s Context API và High-Order-Component, {connect} từ gói ...
Biên dịch: Nguyễn Quốc Đại
Có thể bạn đã gặp qua React’s Context API mà không nhận ra nó, nó tương tự với High-Order-Components.
Nếu bạn đã từng làm việc với Redux, bạn sẽ có kinh nghiệm cả hai, React’s Context API và High-Order-Component, {connect} từ gói react-redux của họ. Hoặc @material-ui/core với withStyles.
Cả hai khái niệm này đều không khó nhưng tôi nghĩ việc nhận ra khi sử dụng chúng mới khó khăn.
Trong ứng dụng tôi đang làm việc, tôi đã có một đối tượng client được sử dụng để lưu trữ các giá trị mặc định và cài đặt mặc định của hệ thống cũng như các đối tượng có thể tái sử dụng (ứng dụng tôi xây dựng cũng có khái niệm về một middlware để các đối tượng trong đó có thể thay đổi dựa trên middlewares injected).
Script
Chúng tôi sẽ làm việc trên “theme” cho ứng dụng của chúng tôi. Chúng tôi có một đối tượng lưu trữ chủ đề mặc định của chúng tôi mà các trang của chúng tôi có thể sử dụng.
Kịch bản này có thể được xây dựng hoàn toàn với React’s Context API.
Context API
Hãy bắt đầu bằng cách tạo một tệp có tên ThemeContext.js.
1 2 3 4 |
<strong>import React from 'react'; export default React.createContext();</strong> |
Trong mục nhập của ứng dụng của chúng tôi, chúng tôi muốn phần renders của chúng tôi như sau:
1 2 3 4 5 6 7 8 9 10 11 12 |
import React from 'react'; <strong>import ThemeContext from './ThemeContext'; </strong>const App = () => { return ( <strong> <ThemeContext.Provider value="dark"> </strong> <Welcome /> <strong> </ThemeContext.Provider> </strong> ) } ReactDOM.render(<App />, document.getElementById("root")); |
Và trong Welcome component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import React from 'react'; <strong>import ThemeContext from './ThemeContext'; </strong>const Welcome = () => { <strong><ThemeContext.Consumer> {theme => {</strong> return ( <div style={{background: theme === "dark" ? "#000" : "#fff"}}> <h2 style={{color: theme !== "dark" ? "#000" : "#fff"}}>Welcome</h2> </div> ) }} <strong> </ThemeContext.Consumer> </strong>} export default Welcome; |
Điều này thể hiện sự đơn giản của React’s Context API. Trước tiên, chúng tôi đã xác định React’s Context API của mình. Chúng tôi xác định các Provider trong thành phần ứng dụng của chúng tôi ở đây vì điều này sẽ đảm bảo rằng context được cung cấp cho tất cả các component con khi cần thiết.
Tôi nghĩ rằng đó là điều bạn nên biết, nếu Provider không được xác định cao hơn consumer, thì bạn sẽ không bao giờ có thể nhận được các giá trị từ bên trong.
Tiếp theo, chúng tôi xác định consumer của mình, ở đây chúng tôi có thể truy cập context value theme mà chúng tôi có thể sử dụng để tùy chỉnh các component của chúng tôi.
Ví dụ này không khó về mặt kỹ thuật và bạn có thể thoát ra chỉ bằng cách chuyển sang chủ đề như là biện pháp phòng ngừa trong trường hợp này. Tuy nhiên, bạn cần phải tưởng tượng rằng trong một ứng dụng lớn, bạn có thể có hàng tá component cần thiết để truy cập vào giá trị này một cách khác nhau.
Higher-Order Components
Cá nhân tôi ghét cú pháp được sử dụng để sử dụng API Context Consumer và nó có thể mang lại sự mệt mỏi. Ngoài ra nếu theme object của tôi phức tạp, tôi có thể không cần tất cả các giá trị từ nó vì vậy tôi muốn có khả năng lựa chọn khi tôi cần chúng.
Giả sử theme object của tôi trông giống như sau:
1 2 3 4 5 6 7 8 9 10 11 |
const theme = { background: { color: { primary: "#f00", secondary: "#f0f" } } <strong>...// more items here };</strong> |
Nếu tôi cần truy cập bất cứ thứ gì ở đây, các component của tôi sẽ bắt đầu trở nên thực sự lộn xộn. Điều gì làm cho nó tồi tệ hơn là nếu bạn có một Context khác mà bạn đang cố truy cập. Sự lồng ghép này sẽ trở nên khủng khiếp.
Vì vậy, hãy tạo Higher-Order Component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import React from "react"; import ThemeContext from "./ThemeContext"; const defaultMergeProps = (themeProps) => ({ ...themeProps }); export const connect = (mergeProps = defaultMergeProps) => ComponentToWrap => { return class ClientComponent extends React.Component { render() { return ( <ThemeContext.Consumer> {theme => { const props = {...mergeProps({ theme }), ...this.props}; return <ComponentToWrap {...props} />; }} </ThemeContext.Consumer> ); } }; }; export default connect; |
Ở đây, chúng tôi đang tạo HOC của chúng tôi, được gọi là connect. Điều này có một hàm để chuyển đổi các thuộc tính được truyền vào component của chúng ta. Điều này cũng trả về một hàm có trong component của chúng ta và đưa các thuộc tính vào nó.
Hãy chia nhỏ thêm một chút nữa.
Chúng tôi có function defaultMergeProps có trong theme props. Function mặc định là chỉ trả về các giá trị của theme object.
Bây giờ bạn có thể thấy rằng trong hàm trả về, chúng ta sử dụng Context API (our ThemeContext) consumer và truyền vào theme object xuống hàm mergeProps. Điều này cho phép chúng tôi tùy chỉnh cách chúng tôi sử dụng để giải nén các
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React from 'react'; import { connect } from './connect'; const Welcome = ({primaryBackground, primaryText}) => { return ( <div style={{background: primaryBackground}}> <h2 style={{color: primaryText}}>Welcome</h2> </div> ) } const mergeProps = (theme) => ( { primaryBackground: theme.background.primary, primaryText: theme.text.color.primary } ); export default connect(mergeProps)(Welcome); |
Như bạn có thể thấy, thành phần chính nó là nhiều neater. Chúng tôi giải nén các thuộc tính chúng tôi cần từ các theme props và đưa chúng vào component của chúng tôi giống như chúng là các thuộc tính thông thường.
Như bạn có thể thấy, hai mô hình này có lợi thế nhưng tôi sẽ không đi lung tung. Nhiều khả năng, bạn sẽ không cần phải làm việc với nó và nhu cầu của bạn có thể được đáp ứng với các gói như Redux. Nhưng nếu bạn định làm điều đó, việc áp dụng hai mẫu chắc chắn sẽ giúp giữ cho các component của bạn đơn giản và rõ ràng hơn để đọc.
Provider
Nếu bạn đang phát triển gói này được các nhóm khác sử dụng và có khả năng tạo nhiều dự án, tôi cũng sẽ tạo một Provider component riêng biệt liên quan đến Context Provider thực tế, theme này sẽ một là tính năng.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import React from "react"; import ThemeContext from "./ThemeContext"; export default class Provider extends React.Component { render() { return ( <ThemeContext.Provider value={this.props.theme}> {this.props.children} </ThemeContext.Provider> ); } } |
Và trong file App.js, chúng tôi có thể ẩn việc thực hiện căn bản với việc sử dụng Provider này.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import React from 'react'; import Provider from './Provider'; const App = () => { let theme = {...}; return ( <strong> <Provider theme={theme}> </strong> <Welcome /> <strong> </Provider> </strong> ) } ReactDOM.render(<App />, document.getElementById("root")); |
Happy coding anh em!
TopDev via Medium