react 高阶组件

是什么

高阶组件并不是 React API,只是一种编程技巧而已。

从形式上看,高阶组件是参数返回值都是组件纯函数

从功能上看,高阶组件就是给原组件添加功能,将其转变成新的组件。

解决什么问题

HOC 可以用来解决“横切关注点问题”,即不同组件间逻辑复用的问题。

可以将组件间可复用的逻辑使用 HOC 来封装起来,各个组件将自己传递给 HOC 后,HOC 将返回新的添加了特定功能的组件。

约定

props 传递

高阶组件需要将与自己无关的 props 传递给被包装的组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render() {
// 过滤掉 HOC 额外的 props
const { extraProp, ...passThroughProps } = this.props;

// 将 props 注入到被包装的组件中。
// 通常为 state 的值或者实例方法。
const injectedProp = someStateOrInstanceMethod;

// 将 props 传递给被包装组件
return (
<WrappedComponent
injectedProp={injectedProp}
{...passThroughProps}
/>
);
}

调试

需要设置新组件的显示名称,以方便调试

1
2
3
4
5
6
7
8
9
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {/* ... */}
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
return WithSubscription;
}

function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

注意事项

不要在 render 中调用 HOC

可以在组件的生命周期方法中调用HOC,但不要在 render 中调用 HOC,否则将导致子树每次渲染都会进行卸载和重新挂载的操作!

1
2
3
4
5
6
7
8
9
10
11
/*
不要在 render 中调用 HOC
*/
render() {
// 下面语句会使得每次执行 render 函数都会创建一个新的 EnhancedComponent
// 导致 EnhancedComponentOld !== EnhancedComponentNew
// 进而导致子树每次渲染都会进行卸载和重新挂载的操作!

const EnhancedComponent = enhance(MyComponent);
return <EnhancedComponent />;
}

组件静态方法

需要对组件静态方法进行额外复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
被包装后的组件默认没有原始组件的静态方法
可以用下面两种方式进行处理
*/

//方式一:返回前将静态方法拷贝给包裹后的组件
//可以使用 hoist-non-react-statics 自动拷贝所有非 React 静态方法:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}



//方式二:将原始组件的静态方法额外导出,使用的时候再导入
MyComponent.someFunction = someFunction;
export default MyComponent;

// ...单独导出该方法...
export { someFunction };

// ...并在要使用的组件中,import 它们
import MyComponent, { someFunction } from './MyComponent.js';

Refs 不会被传递

props 中没有包含 ref 属性,故不会被传递,可以通过使用 React.forwardRef API 解决此问题。