React高階組件(譯)

  • 2019 年 12 月 4 日
  • 筆記

原文:https://daveceddia.com/extract-state-with-higher-order-components/

高階組件是對React程式碼進行更高層次重構的好方法,如果你想精簡你的state和生命周期方法,那麼高階組件可以幫助你提取出可重用的函數。

什麼是高階組件?名字來源於高階函數,一個函數可以接收另一個函數作為參數,並且有可能在執行後返回一個函數,這種函數就稱之為高階函數。你可能使用過高階函數但是並沒有真正意識到,例如Array.forEachArray.mapsetTimeout這些都是高階函數,我們都知道這些函數全都是接受一個函數作為參數,當新的函數返回時,他已經發生了變化。

// 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.找出重複的程式碼

每個組件中constructorcomponentDidMount都干著同樣的事情,另外,在數據拉取時都會顯示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

BookDetailsBookSummary組件應用 到新的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);