前言
这篇文章没有堆砌太多的概念,如果对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新增) 解决。