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);