高阶组件
高阶组件就是一个没有副作用的纯函数,并且可以接收另一个函数组件作为参数,返回值也为一个函数组件。
高阶组件其实就是装饰器模式在 React 中的实现:通过给函数传入一个组件(函数或类)后在函数内部对该组件(函数或类)进行功能的增强(不修改传入参数的前提下),最后返回这个组件(函数或类),即允许向一个现有的组件添加新的功能,同时又不去修改该组件,属于 包装模式(Wrapper Pattern) 的一种。
React 中的高阶组件主要有两种形式:属性代理 和 反向继承
属性代理
// 无状态(函数式组件)
function HigherOrderComponent(WrappedComponent) {
return props => <WrappedComponent {...props} />;
}
// 有状态
function HigherOrderComponent(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
就是 一个函数接受另一个组件作为参数传入,并返回一个继承了 React.Component 组件的类,且在该类的 render() 方法中返回被传入的 另一个组件。
因为属性代理类型的高阶组件返回的是一个标准的 React.Component 组件,所以在 React 标准组件中可以做什么,那在属性代理类型的高阶组件中就也可以做什么,比如:
-
操作 props
可以为子组件添加一些属性
-
抽离 state
利用 props 和回调函数把 state 抽离出来,可以实现受控组件
通过 ref 访问到组件实例
-
用其他元素包裹传入的组件
比如再包一层div什么的
反向继承
function HigherOrderComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
};
}
反向继承其实就是 一个函数接受另一个组件作为参数传入,并返回一个继承了该传入组件的类,且在该类的 render() 方法中返回 super.render() 方法。
反向继承可以用来做什么:
-
操作 state
高阶组件中可以读取、编辑和删除传入组件实例中的 state。甚至可以增加更多的 state 项,但是 非常不建议这么做因为这可能会导致 state 难以维护及管理
-
渲染劫持(Render Highjacking)
- 有条件地展示元素树(element tree)
- 操作由 render() 输出的 React 元素树
- 在任何由 render() 输出的 React 元素中操作 props
- 用其他元素包裹传入的组件 WrappedComponent (同 属性代理)
其实属性代理和反向继承的实现有些类似的地方,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component,反向继承中继承的是传入的组件。
高阶组件存在的问题
-
静态方法丢失
因为原始组件被包裹于一个容器组件内,也就意味着新组件会没有原始组件的任何静态方法
-
refs 属性不能透传
如果你向一个由高阶组件创建的组件的元素添加 ref 引用,那么 ref 指向的是最外层容器组件实例的,而不是被包裹的组件。
-
反向继承不能保证完整的子组件树被解析
我们知道反向继承的渲染劫持可以控制传入组件 的渲染过程,也就是说这个过程中我们可以对 elements tree、state、props 或 render() 的结果做各种操作。但是如果渲染 elements tree 中包含了 function 类型的组件的话,这时候就不能操作组件的子组件了。
应用场景
权限控制
利用高阶组件的 条件渲染 特性可以对页面进行权限控制
组件渲染性能追踪
借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录
页面复用
高阶组件的约定
- props 保持一致
- 你不能在函数式(无状态)组件上使用 ref 属性,因为它没有实例
- 不要以任何方式改变原始传入组件
- 不要透传不相关 props 属性给被包裹的组件
- 不要再 render() 方法中使用高阶组件
- 使用 compose 组合高阶组件
- 包装显示名字以便于调试