高阶组件的定义
高阶组件(Higher-Order Component)是一个函数,以下简称 HOC,接受一个组件,返回一个新组件。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC 不应该修改传入组件,而应该使用组合的方式,通过将组件包装在容器组件中实现功能:
1 | import React, { Component } from "react"; |
高阶组件不是只能接收一个参数,它也可以接收多个参数。项目中通常使用的 React Redux 的 connect
函数,它的 HOC 签名是这样的:const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
这样的用法实现方式大概类似以下代码:
1 | import React, { Component } from 'react' |
高阶组件(HOC) 使用场景
- 代码复用:这是高阶组件最基本的功能。组件是 React 中最小单元,两个相似度很高的组件通过将组件重复部分抽取出来,再通过高阶组件扩展,增删改 props,可达到组件可复用的目的;
- 条件渲染:控制组件的渲染逻辑,常见场景:鉴权、新手指导;
- 生命周期捕获/劫持:借助父组件子组件生命周期规则捕获子组件的生命周期,常见场景:打点。
使用高阶组件的注意事项:
虽然高阶组件的约定是将所有 props 传递给被包装组件,因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
1 | const enhanceComponent = HOCComponent(WrappedComponent) |
在应用中如果需要和 dom 交互后者父组件想要调用子组件的某些方法,我们就需要获取 ref。react-redux 中的 connect 在开发中是常用到的高阶组件,如果我们想通过 ref 去拿被 connect 包裹的组件是无法获取到结果的,通常会手动指定一下调用 connect 方法的最后一个参数,指定 withRef 为 true, 这样就能拿到被包裹组件的 ref 了。connect([mapStateToProps], [mapDispatchToProps], [mergeProps], {withRef: true})(CustomComponent)
如果是自定义的 HOC,又希望拿到被包裹组件的 ref,怎么做呢?答案是 React.forwardRef API(React 16.3 中引入), 通过 React.forwardRef 在高阶组件中转发 refs:
1 | function logProps(Component) { |
有时我们在原始组件定义了静态方法,当用 HOC 把原始组件包装后,原始组件的静态方法在 HOC 之后的新组件里是不存在的
1 | // 定义静态函数 |
解决方法是手动复制这些静态方法:
1 | function enhance(WrappedComponent) { |
如果不清楚有哪些静态方法,可以使用 hoist-non-react-statics 这个库自动拷贝所有非 React 静态方法:
1 | import hoistNonReactStatic from 'hoist-non-react-statics'; |
React 的 diff 算法(称为协调)使用组件标识来确定它是应该更新现有子树还是将其丢弃并挂载新子树。 如果从 render 返回的组件与前一个渲染中的组件相同(===),则 React 通过将子树与新子树进行区分来递归更新子树。 如果它们不相等,则完全卸载前一个子树。这不仅仅是性能问题 - 重新挂载组件会导致该组件及其所有子组件的状态丢失。
1 | render() { |