react之高阶组件HOC

1,143 阅读2分钟

HOC高阶组件是一个函数,接受一个组件作为参数,返回一个新的组件

1、高阶组件HOC的特性

组件是将state转UI,即UI=f(state);高阶组件将组件转换成另一个组件,即EnhancedComponent=f(Comonent)。

  • 1、不修改传入的组件,也不会使用继承来复制其行为,是纯函数,没有副作用
  • 2、应该透传与自身无关的 props
  • 3、不要在render方法中使用HOC
  • 4、务必复制静态方法
  • 5、ref不会被传递

2、属性代理(Props Proxy)

  • 操作props
  • 抽象状态state,react-redux中的connect
  • 获取ref
  • 包装组件(用其他组件包裹/功能集成/样式包裹)
const ppHoc = WrappedComponent => class extends React.Component {
    render() {
        // 此处可以修改props,或者注入state,
        // 总之对WrappedComponent而言就是修改了props
        return  <WrappedComponent {...this.props} />
    }
}

3、反向继承(Inheritance Inversion)

  • 渲染劫持 render hijacking
  • 操纵state
const iiHoc = WrappedComponent => class extends WrappedComponent{
    render() {
        const elementTree = super.render();
        const { props } = elementTree;
        // 可以修改props
        const newProps = {aa: 1};

        return React.cloneElement(elementTree, {...props, ...newProps}, elementsTree.props.children)
    }
}

继承该组件,可基于其elementTree进行修改。能力更强,但风险也更高,不建议使用

4、以函数为子组件

高阶组件也有缺点。对原组件的props有了固化的要求。“以函数为子组件”的模式克服高阶组件这种局限而生的。

class CountDown extends React.Component{
	constructor(props){
    	super(props);
        this.state = {
        	count: this.props.startCount,
        }
    }
	componentDidMount(){
    	this.intervalTimer = setInterval(()=>{
        	if(this.state.count >= 0){
            	this.setState({count: this.state.count+1});
            } else {
            	clearInterval(this.intervalTimer);
            }
        }, 1000)
    }
    componentWillUnmount(){
    	clearInterval(this.intervalTimer);
    }
	render(){
    	return this.props.children(this.state.count);
    }
}

<CountDown startCount={10}>
	{(count) => <div>{count}</div>}
</CountDown>

<CountDown startCount={10}>
	{(count) => <Bomb countDown={count}/>}
</CountDown>

5、实际应用

高阶组件的应用:react-redux中的connect

// React Redux 的 `connect` 函数
const NewMyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponent);

//等价于

// connect 是一个函数,它的返回值为另外一个函数。
const enhance = connect(mapStateToProps, mapDispatchToProps);
// 返回值为 HOC,它会返回已经连接 Redux store 的组件
const NewMyComponent = enhance(MyComponent);

const doNothing = () => ({});
function connect(mapStateToProps=doNothing, mapDispatchToProps=doNothing){
	return function(WrappedComponent){
    	return HOCComponent extends React.Component{
        	constructor(props){
            	super(props);
                this.onChange = this.onChange.bind(this);
            }
            componentDidMount(){
            	this.context.store.subscribe(this.onChange)
            }
            componentWillUnmount(){
            	this.context.store.unsubscribe(this.onChange);
            }
            onChange(){
            	this.setState({});
            }
            render(){
            	const store = this.context.store;
                const newProps = {
                	...this.props,
                    mapStateToProps(store.getState()),
                    mapDispatchToProps(store.dispatch),
                };
                return <WrappedComponent {...newProps}/>
            }
        }
    }
}

高阶组件的应用:发布订阅中

// 此函数接收一个组件
function withSubscription(WrappedComponent, selectData) {
  // 并返回另一个组件
  class Enhance 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() {
      // 并使用新数据渲染被包装的组件!
      // 请注意,我们可能还会传递其他属性
      // ref接收
      const {forwardedRef, ...rest} = this.props;
      
      return <WrappedComponent data={this.state.data} {...rest} ref={forwardedRef}/>;
    }
  };
  
  //必须准确知道应该拷贝哪些方法 
  Enhance.staticMethod = WrappedComponent.staticMethod;
  //ref转发
  return React.forwardRef((props, ref) => {
        return  <Enhance {...props} forwardedRef={ref}/>
  });
}

//使用
const CommentListWithSubscription = withSubscription(
  CommentList, (DataSource) => DataSource.getComments()
);

const ref = React.createRef();
<CommentListWithSubscription ref={ref}/>

参考地址

高阶组件

React HOC