前言

这篇文章没有堆砌太多的概念,如果对React高阶组件不了解可以先移步:高阶组件 – React,这里是React官方对高阶组件的介绍。写这篇文章之前也在网上翻看了一些高阶组件的相关,结合自己对高阶组件的使用经验,谈谈对它的一些看法。

关于mixin,文章里也就不再多做介绍了,它已经算是过时的方案了,这点在官方文档中有相关解释。

为何要使用高阶组件?

为了复用代码?

从复用性的角度来思考,高阶组件其实不是一种很常用的解决方案,封装utils函数、公共组件才是更常见的代码复用方案。目前还没有发现一定要通过使用高阶组件来提升复用性的场景。

简单来说,可以通过封装高阶组件来复用代码,但是没必要。

条件渲染

例如:登录/非登录态的判断。比方说我们需要给项目里的部分页面增加登录限制,但由于前期需求分析不到位,没有对这些页面做控制,现在我们希望使用一种尽量不修改原有代码、与业务解耦的方式来实现,就这时就可以利用到高阶组件。

高阶组件返回一个新组件,包裹住原传入组件,然后根据登录态判断是展示原组件还是展示登录组件。

export default function LoginHOC(WrappedComponent) {
    return class extends React.Component {
        constructor(props) {
            super(props);
            const token = localStorage.getItem('token');
            this.state = {
                login: Boolean(token)       // 是否登录
            };
        }
        render() {
            return this.state.login ?
                <WrappedComponent {...this.props} /> : <Login />;
        }
    }
}

想到之前做过的一个公众号需求,公众号/非公众号页面放在了一个项目里面,然后需要对公众号页面做统一的静默授权判断。当时的做法是用一个统一的Container组件包裹住页面做授权判断,和上面的例子很类似,但是没有运用到高阶组件,需要对每个组件都做类似的改造。如果使用了高阶组件,将减少大量的重复代码,并且在需要改动时可以统一改动。

利用组件生命周期函数

例如:页面初始化时统一做PV、UV统计。同样是高阶组件返回一个新组件,包裹住原传入组件,然后在 componentDidMount 中做数据统计。

总结:使用 HOC 解决横切关注点问题

使用方式

1.属性代理

export default function InputHOC(WrappedComponent) {
  class WrapperComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: 'test'
      };
    }
    
    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  }
  
  return WrapperComponent;
}

这种方式通过将组件包装在容器组件中来组成新组件,接收来自容器组件的所有 prop,并且容器组件新生成了一个 data prop。这种属性代理的方式是没有副作用的,不会修改原组件,也不会使用继承来复制其行为。

2.反向继承

export default function InputHOC(OriginalComponent) {
  class EnhancedComponent extends OriginalComponent {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }
  }
  
  return EnhancedComponent;
}

EnhancedComponent 继承了 OriginalComponent,因此前者获得了后者的方法和属性,均可以通过 this.xxx 来调用。

反向继承相比于属性代理的方式,无疑取得了更大的访问和修改权限,这种方式最大的作用就是覆写父组件的生命周期。之前有做过一个项目,有大概十几二十个页面需要统一修改,利用组件的生命周期做一些事情,先调用原组件的 componentDidUpdate 方法,然后在其基础上增强功能,这时候反向继承就很适合用来实现这个功能。

常见使用误区

  • 不要改变原组件,这样做会产生一些不良后果。其一是输入组件再也无法像 HOC 增强之前那样使用了。更严重的是,如果你再用另一个同样会修改 componentWillReceiveProps 的 HOC 增强它,那么前面的 HOC 就会失效!从这点来讲的话,React官方是不推荐反向继承这种方式的,但是反向继承确实可以实现更强大的功能,因此具体使用与否先不下定论。
function logProps(InputComponent) {
  InputComponent.prototype.componentWillReceiveProps = function(nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);
  };
  // 返回原始的 input 组件,暗示它已经被修改。
  return InputComponent;
}

// 每次调用 logProps 时,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent);
  • 不要在 render 方法中使用 HOC,因为每次执行 render 都会生成新的 HOC 组件,这不仅仅是性能问题,重新挂载组件会导致该组件及其所有子组件的状态丢失。

  • ref 不能直接像 prop 一样传递,可以通过 React.forwardRef (React 16.3新增) 解决。

参考:
高阶组件 – React
React高阶组件实践 – 掘金
深入浅出React高阶组件

最后修改日期:2019年10月1日

作者