1. 程式人生 > >React中的高階元件(HOC)

React中的高階元件(HOC)

簡要介紹:React中不會用到元件的繼承,作者選擇用組合來代替繼承,

但是存在一種情況,就是兩種元件方法類似,如果能有一種“類繼承”的

方式,在同一個函式中可以生產這兩種元件,那麼就可以大量的減少程式碼

的冗餘,在React的高階元件實現了Decorator 模式來模擬繼承或者說來

代替繼承。

1、什麼是高階元件(high order component)

高階元件的定義:就是一個函式,這個函式傳入一個元件,然後返回一個新的元件。

高階元件在react的一些外掛中大量使用,比如react-redux中的connect方法,就是接受了一個展示元件,輸出一個容器元件。

2、為什麼要使用高階元件

主要原因:為了程式碼的複用性,減少程式碼的冗餘

下面來具體闡述為什麼需要使用高階元件(以官網文件的例子為例):

(1)CommentList元件

class CommentList extends React.Component {
  constructor() {
    super();
    this.handleChange = this.handleChange.bind(this);
    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> ); } }

這個元件很簡單,就是從遠端獲取資料展示一系列的comment,並且監聽資料是否發生變化。

(2)BlogPost元件

class BlogPost extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    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} />;
  }
}

我們來看BlogPost元件,幾乎與comment元件一樣,也是從遠端獲取資料然後展示TextBlock列表。

(3)比較BlogPost和Comment元件

我們發現,這倆個元件的在生命週期中很多函式是相同的,僅僅是在render返回的子元件型別不同。它們又是不同的,比如BlogPost呼叫的是DataSource.getBlogPost方法而Comment呼叫的是DataSource.getComments方法。

缺點:如果在某個場景下,需要成千上百個這種相似元件,那麼我們可能要手工生成這麼成千上百個元件,程式碼的冗餘程度可想而知。

(4)解決方法

如果我們定義一個函式,引數是元件和函式,生成了不同的元件(不同而又相似)。那麼這個函式就是類似於一個工廠的形式,可以批量的產生各種相似的元件,函式類似於下面這種形式。

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

具體的函式為:

// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
  // ...and returns another component...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ... that takes care of the subscription...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... and renders the wrapped component with the fresh data!
      // Notice that we pass through any additional props
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

3、高階元件中的注意事項

(1)首先因為高階元件是一個函式,因此你可以傳遞任意的引數。

(2)不要修改原始元件的方法

(3)不要在render函式中使用HOC:

render() {
  // A new version of EnhancedComponent is created on every render
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // That causes the entire subtree to unmount/remount each time!
  return <EnhancedComponent />;
}

相當於重新定義和使用了元件,render方法會全部重新執行,這樣就無法利用react的diff演算法,更新式的重新渲染元件。

(4)必須重寫引數元件中的靜態方法

如果不重寫靜態方法,那麼高階元件的返回的那個元件上同名靜態方法不存在,或為undefined

// Define a static method
WrappedComponent.staticMethod = function() {/*...*/}
// Now apply an HOC
const EnhancedComponent = enhance(WrappedComponent);

// The enhanced component has no static method
typeof EnhancedComponent.staticMethod === 'undefined' // true

為了解決上述問題,我們必須將方法拷貝到返回的新的元件中:

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // Must know exactly which method(s) to copy :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

(5)注意ref保留字