高阶组件(HOC)是react中对组件逻辑进行重用的高级技术,但高阶组件本身并不是react API,它只是一种模式,这种模式是由react自身组合性质必然产生的
具体而言,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件
高阶组件既不会修改input原组件,也不会使用继承复制input原组件的行为。相反,高阶组件是通过将原组件 包裹(wrapping) 在容器组件(container component)里面的方式来 组合(composes) 使用原组件。高阶组件就是一个没有副作用的纯函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
高阶组件在React第三方中很常见,比如react-redux中的connect方法和Relay的createContainer
OneList组件
import React from 'react';
class OneList extends React.Component {
constructor(props) {
super(props);
this.state = {
username: ''
}
}
componentWillMount() {
let username = localStorage.getItem('username');
this.setState({
username: username
});
}
render() {
return(
<div>
<legend>OneList Page</legend>
<h2>Hi {this.state.username}</h2>
</div>
)
}
}
export default OneList;
OtherList组件
import React from 'react';
class OtherList extends React.Component {
constructor(props) {
super(props);
this.state = {
username: ''
}
}
componentWillMount() {
let username = localStorage.getItem('username');
this.setState({
username: username
});
}
render() {
return(
<div>
<legend>OtherList Page</legend>
<h2>Hi {this.state.username}</h2>
</div>
)
}
}
export default OtherList;
我们看到这两个组件名称不同之外,大量的代码都是一样的,我们可以把通用的代码提取出来,可以用高阶组件上场,以下是个高阶组件处理OneList和OtherList
import React from 'react';
const HighOrderComponent = (wrapComponent,title) =>{
return class HOC extends React.Component{
constructor(props){
super(props);
this.state = {
username: ''
}
}
componentWillMount() {
let username = localStorage.getItem('username');
this.setState({
username: username
});
}
render(){
return(
<div>
<legend>{title}</legend>
<WrapComponent username={this.state.username}></WrapComponent>
</div>
)
}
}
}
export default HighOrderComponent;
如何运用上面的组件
import React from 'react';
import HighOrderComponent from './HighOrderComponent.jsx';
class OneList extends React.Component {
render() {
return(
<div>
<h2>Hi </h2>
<h2>晓不晓得哪里好耍{this.props.username}</h2>
</div>
)
}
}
const HighOrderOne = HighOrderComponent(SecHoc, 'Second Page');
export default HighOrderOne
HighOrderOne 就是一个经过高阶组件之后生成一个全新的组件
CommentList组件
class CommentList extends React.Component {
constructor() {
super();
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 的不同方法获取数据,并且他们渲染的输出结果也不相同。但是,他们的大部分实现逻辑是一样的
我们可以创建一个withSubscription组件,把公共部分封装起来,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() {
// ……使用最新的数据渲染组件
// 注意此处将已有的props属性传递给原组件
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
使用高阶组件,生成全新的一个组件
import React form 'react';
import withSubscription = './withSubscription .jsx'
//CommentList
class CommentList extends React.Component {
render() {
return (
<div>
{this.state.comments.map((comment) => (
<Comment comment={comment} key={comment.id} />
))}
</div>
);
}
}
//BlogPost
class BlogPost extends React.Component {
render() {
return <TextBlock text={this.state.blogPost} />;
}
}
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
//第一个参数是包裹组件(wrapped component),第二个参数会从 DataSource和当前props属性中检索应用需要的数据。
const BlogPostWithSubscription = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)
);
export {CommentListWithSubscription,BlogPostWithSubscription }
CommentListWithSubscription,BlogPostWithSubscription两个组件就是经过高阶组件之后生成一个全新的组件。
约定:
1.不要改变原始组件,使用组合
2.将不相关的props属性传递给包裹组件
render() {
// 过滤掉与高阶函数功能相关的props属性,
// 不再传递
const { extraProp, ...passThroughProps } = this.props;
// 向包裹组件注入props属性,一般都是高阶组件的state状态
// 或实例方法
const injectedProp = someStateOrInstanceMethod;
// 向包裹组件传递props属性
return (
<WrappedComponent
injectedProp={injectedProp}
{...passThroughProps}
/>
);
}
3.最大化使用组合
4.包装显示名字以便于调试
注意事项:
1.不要在render函数中使用高阶组件
2.必须将静态方法做拷贝
//当使用高阶组件包装组件,原始组件被容器组件包裹,也就意味着新组件会丢失原始组件的所有静态方法。
// 定义静态方法
WrappedComponent.staticMethod = function() {/*...*/}
// 使用高阶组件
const EnhancedComponent = enhance(WrappedComponent);
// 增强型组件没有静态方法
typeof EnhancedComponent.staticMethod === 'undefined' // true
//解决办法,将原始组件的所有静态方法全部拷贝给新组件
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// 必须得知道要拷贝的方法 :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
//你可以使用hoist-non-react-statics来帮你自动处理,它会自动拷贝所有非React的静态方法:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
//另外一个可能的解决方案就是分别导出组件自身的静态方法。
// 替代……
MyComponent.someFunction = someFunction;
export default MyComponent;
// ……分别导出……
export { someFunction };
// ……在要使用的组件中导入
import MyComponent, { someFunction } from './MyComponent.js';
3.Refs属性不能传递