React高階組件(譯)
- 2019 年 12 月 4 日
- 筆記
原文:https://daveceddia.com/extract-state-with-higher-order-components/
高階組件是對React程式碼進行更高層次重構的好方法,如果你想精簡你的state和生命周期方法,那麼高階組件可以幫助你提取出可重用的函數。
什麼是高階組件?名字來源於高階函數,一個函數可以接收另一個函數作為參數,並且有可能在執行後返回一個函數,這種函數就稱之為高階函數。你可能使用過高階函數但是並沒有真正意識到,例如Array.forEach
、Array.map
、setTimeout
這些都是高階函數,我們都知道這些函數全都是接受一個函數作為參數,當新的函數返回時,他已經發生了變化。
// Ok :) setTimeout(function() { // do a thing after 500ms }, 500); // Sure... [1, 2, 3].map(function(i) { // multiply each element by 2 return i * 2; }); // Wait what? function middleware(store) { return function(next) { return function(action) { // do the thing } } }
那麼,什麼又是高階組件呢?其實就是把一個組件接收一個組件作為參數,並返回包裹後的組件。既然可以把另一個組件作為參數,那意味著他必須是一個函數。
我們先來看一個典型的高階組件:
// It's a function... function myHOC() { // Which returns a function that takes a component... return function(WrappedComponent) { // It creates a new wrapper component... class TheHOC extends React.Component { render() { // And it renders the component it was given return <WrappedComponent {...this.props} />; } } // Remember: it takes a component and returns a new component // Gotta return it here. return TheHOC; } }
提取共享的state
如果有兩個組件都需要載入同樣的數據,那麼他們會有相同的 componentDidMount 函數。
//BookDetails.js import React, { Component } from 'react'; import * as API from '../api'; // let's just pretend this exists class BookDetails extends Component { constructor(props) { super(props); this.state = { book: null }; } componentDidMount() { API.getBook(this.props.bookId).then(book => { this.setState({ book }); }) } render() { const { book } = this.state; if(!book) { return <div>Loading...</div>; } return ( <div> <img src={book.coverImg}/> <div>{book.author}</div> <div>{book.title}</div> </div> ); } } export default BookDetails;
// BookSummary.js import React, { Component } from 'react'; import * as API from '../api'; // let's just pretend this exists class BookSummary extends Component { constructor(props) { super(props); this.state = { book: null }; } componentDidMount() { API.getBook(this.props.bookId).then(book => { this.setState({ book }); }) } render() { const { book } = this.state; if(!book) { return <div>Loading...</div>; } return ( <div> <div>{book.summary}</div> </div> ); } } export default BookSummary;
1.找出重複的程式碼
每個組件中constructor
和 componentDidMount
都干著同樣的事情,另外,在數據拉取時都會顯示Loading...
文案,那麼我們應該思考如何使用高階組件來提取這些方法。
2.遷移重複的程式碼到高階組件
// BookLoader.js import * as API from 'api'; // let's just pretend this exists // It's a function... function loadBook() { // Which returns a function that takes a component... return function(WrappedComponent) { // It creates a new wrapper component... class BookLoader extends React.Component { // Here's the duplicated code from above: constructor(props) { super(props); this.state = { book: null }; } componentDidMount() { API.getBook(this.props.bookId).then(book => { this.setState({ book }); }) } render() { const { book } = this.state; if(!book) { return <div>Loading...</div>; } // Notice how "book" is passed as a prop now return ( <WrappedComponent {...this.props} book={book} /> ); } } // Remember: it takes a component and returns a new component // Gotta return it here. return BookLoader; } } export default loadBook;
在 BookLoader 高階組件中處理 book
state,並且作為prop傳遞給已包裹的組件,使用相同的辦法來處理Loading
state,我們需要做的是拉取state,並且更新到組件中去。
3.包裹組件,並且使用props替換state
把 BookDetails
和 BookSummary
組件應用 到新的BookLoader
高階組件中去:
// BookDetails.js import React, { Component } from 'react'; import loadBook from './BookLoader'; class BookDetails extends Component { render() { // Now "book" comes from props instead of state const { book } = this.props; return ( <div> <img src={book.coverImg}/> <div>{book.author}</div> <div>{book.title}</div> </div> ); } } export default loadBook()(BookDetails);
// BookSummary.js import React, { Component } from 'react'; import loadBook from './BookLoader'; class BookSummary extends Component { render() { // Now "book" comes from props instead of state const { book } = this.props; return ( <div> <div>{book.summary}</div> </div> ); } } export default loadBook()(BookSummary);
4.儘可能地簡化
在你完成高階組件遷移後,應該更進一步地簡化你的程式碼,這個例子的組件比較簡單,他們可以變成普通的功能:
// BookDetails.js import loadBook from './BookLoader'; function BookDetails({ book }) { return ( <div> <img src={book.coverImg}/> <div>{book.author}</div> <div>{book.title}</div> </div> ); } export default loadBook()(BookDetails);
// BookSummary.js import loadBook from './BookLoader'; function BookSummary({ book }) { return ( <div> <div>{book.summary}</div> </div> ); } export default loadBook()(BookSummary);