简要介绍: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保留字