1 高阶组件
- 高阶组件是参数为组件,返回值为
新组件
的函数
- 组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件
- HOC是 React 中用于复用组件逻辑的一种高级技巧; 是一种组件组合的设计模式
const EnhancedComponent = higherOrderComponent(WrappedComponent);
2 高阶组件示例
// CommentList组件
class CommentList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
// 假设 "DataSource" 是个全局范围内的数据源变量
comments: DataSource.getComments()
};
}
componentDidMount() {
// 订阅更改
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
// 清除订阅
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
// 当数据源更新时,更新组件状态
this.setState({
comments: DataSource.getComments()
});
}
render() {
return (
<div>
{this.state.comments.map((comment) => (
<Comment comment={comment} key={comment.id} />
))}
</div>
);
}
}
// 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} />;
}
}
// CommentList 和 BlogPost组件 不同 - 它们在 DataSource 上调用不同的方法,且渲染不同的结果。但它们的大部分实现都是一样的:
// 1. 在挂载时,向 DataSource 添加一个更改侦听器。
// 2. 在侦听器内部,当数据源发生变化时,调用 setState。
// 3. 在卸载时,删除侦听器。
// 对于订阅了 DataSource 的组件,比如 CommentList 和 BlogPost,我们可以编写一个创建组件函数。该函数将接受一个子组件作为它的其中一个参数,该子组件将订阅数据作为 prop。让我们调用函数 withSubscription
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
const BlogPostWithSubscription = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)
);
// 第一个参数是被包装组件。第二个参数通过 DataSource 和当前的 props 返回我们需要的数据。
// 高阶组件withSubscription实现
function withSubscription(WrappedComponent, selectData) {
// ...并返回另一个组件...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// ...负责订阅相关的操作...
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// ... 并使用新数据渲染被包装的组件!
// 请注意,我们可能还会传递其他属性
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
3 高阶组件注意点
function logProps(InputComponent) {
InputComponent.prototype.componentDidUpdate = function(prevProps) {
console.log('Current props: ', this.props);
console.log('Previous props: ', prevProps);
};
// 返回原始的 input 组件,暗示它已经被修改。
return InputComponent;
}
// 每次调用 logProps 时,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent);
// 解析:不要试图在 HOC 中修改组件原型(或以其他方式改变它); 比如修改了原始组件的原型,其一:那么输入组件再也无法像 HOC 增强之前那样使用了;其二:如果你再用另一个同样会修改 componentDidUpdate 的 HOC 增强它,那么前面的 HOC 就会失效!
// 组件组合:HOC 不应该修改传入组件,而应该使用组合的方式,通过将组件包装在容器组件中实现功能
function logProps(WrappedComponent) {
return class extends React.Component {
componentDidUpdate(prevProps) {
console.log('Current props: ', this.props);
console.log('Previous props: ', prevProps);
}
render() {
// 将 input 组件包装在容器中,而不对其进行修改。Good!
return <WrappedComponent {...this.props} />;
}
}
}
虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件