高阶组件的约定

高阶组件带给我们极大方便的同时,我们也要遵循一些 约定:

  • props 保持一致
  • 你不能在函数式(无状态)组件上使用 ref 属性,因为它没有实例
  • 不要以任何方式改变原始组件 WrappedComponent
  • 透传不相关 props 属性给被包裹的组件 WrappedComponent
  • 不要再 render() 方法中使用高阶组件
  • 使用 compose 组合高阶组件
  • 包装显示名字以便于调试
props 保持一致

高阶组件在为子组件添加特性的同时,要尽量保持原有组件的 props 不受影响,也就是说传入的组件和返回的组件在 props 上尽量保持一致。

不要改变原始组件 WrappedComponent

不要在高阶组件内以任何方式修改一个组件的原型,思考一下下面的代码:

1
2
3
4
5
6
7
8
function withLogging(WrappedComponent) {
WrappedComponent.prototype.componentWillReceiveProps = function(nextProps) {
console.log('Current props', this.props);
console.log('Next props', nextProps);
}
return WrappedComponent;
}
const EnhancedComponent = withLogging(SomeComponent);

会发现在高阶组件的内部对 WrappedComponent 进行了修改,一旦对原组件进行了修改,那么就失去了组件复用的意义,所以请通过 纯函数(相同的输入总有相同的输出) 返回新的组件

1
2
3
4
5
6
7
8
9
10
11
12
function withLogging(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps() {
console.log('Current props', this.props);
console.log('Next props', nextProps);
}
render() {
// 透传参数,不要修改它
return <WrappedComponent {...this.props} />;
}
};
}

这样优化之后的 withLogging 是一个 纯函数,并不会修改 WrappedComponent 组件,所以不需要担心有什么副作用,进而达到组件复用的目的。

1
2
3
4
5
6
7
function HigherOrderComponent(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent name="name" {...this.props} />;
}
};
}
不要在 render() 方法中使用高阶组件
1
2
3
4
5
6
7
8
9
class SomeComponent extends React.Component {
render() {
// 调用高阶函数的时候每次都会返回一个新的组件
const EnchancedComponent = enhance(WrappedComponent);
// 每次 render 的时候,都会使子对象树完全被卸载和重新
// 重新加载一个组件会引起原有组件的状态和它的所有子组件丢失
return <EnchancedComponent />;
}
}
使用 compose 组合高阶组件
1
2
3
4
5
6
// 不要这么使用
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent));
// 可以使用一个 compose 函数组合这些高阶组件
// lodash, redux, ramda 等第三方库都提供了类似 `compose` 功能的函数
const enhance = compose(withRouter, connect(commentSelector));
const EnhancedComponent = enhance(WrappedComponent);

因为按照 约定 实现的高阶组件其实就是一个纯函数,如果多个函数的参数一样(在这里 withRouter 函数和 connect(commentSelector) 所返回的函数所需的参数都是 WrappedComponent),所以就可以通过 compose 方法来组合这些函数。

使用 compose 组合高阶组件使用,可以显著提高代码的可读性和逻辑的清晰度。

包装显示名字以便于调试

高阶组件创建的容器组件在 React Developer Tools 中的表现和其它的普通组件是一样的。为了便于调试,可以选择一个显示名字,传达它是一个高阶组件的结果。

1
2
3
4
5
6
const getDisplayName = WrappedComponent => WrappedComponent.displayName || WrappedComponent.name || 'Component';
function HigherOrderComponent(WrappedComponent) {
class HigherOrderComponent extends React.Component {/* ... */}
HigherOrderComponent.displayName = `HigherOrderComponent(${getDisplayName(WrappedComponent)})`;
return HigherOrderComponent;
}

实际上 recompose 库实现了类似的功能,懒的话可以不用自己写:

1
2
3
4
5
import getDisplayName from 'recompose/getDisplayName';
HigherOrderComponent.displayName = `HigherOrderComponent(${getDisplayName(BaseComponent)})`;
// Or, even better:
import wrapDisplayName from 'recompose/wrapDisplayName';
HigherOrderComponent.displayName = wrapDisplayName(BaseComponent, 'HigherOrderComponent');