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保留字

猜你喜欢

转载自blog.csdn.net/liwusen/article/details/75758127