高阶组件的约定
高阶组件带给我们极大方便的同时,我们也要遵循一些 约定:
- 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); return <EnchancedComponent />; } }
|
使用 compose 组合高阶组件
1 2 3 4 5 6
| const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent));
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)})`;
import wrapDisplayName from 'recompose/wrapDisplayName'; HigherOrderComponent.displayName = wrapDisplayName(BaseComponent, 'HigherOrderComponent');
|