高阶组件(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属性不能传递

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐