React高阶组件使用教程详解

 更新时间:2022年12月4日 14:29  点击:298 作者:码农小菲

高阶组件(HOC)

概述

是React复用组件逻辑的一种高级技巧,是一种基于React组合特性而形成的设计模式

高阶组件是参数为组件,返回值为新组件的函数

简单理解:

  • 高阶组件本身是 函数,传参数是组件,返回值也是组件;
  • 高阶组件不用关心数据是如何渲染的,只用关心逻辑即可
  • 被包装的组件本身不用关心数据是怎么来的,只用负责渲染即可
  • 最后渲染的是高阶组件返回的组件

高阶组件的调用过程类似于这样:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

应用场景:redux 中的 connect

具体怎么编写呢?往下看…

使用HOC解决横切关注点问题

横切关注点问题:指的是一些具有横越多个模块的行为,使用传统的软件开发方法不能够达到有效的模块化的一类特殊关注点。

组件是React 中代码复用的基本单元,但某些模式并不适合传统组件

假设有一个 CommentList 组件,订阅外部数据源,用于渲染评论列表:

class CommentList extends React.Component {
   constructor(props) {
     super(props);
     this.handleChange = this.handleChange.bind(this);
     this.state = {
       // 假设 "DataSource" 是个全局范围内的数据源变量,来自外部,自身带有很多方法
       comments: DataSource.getComments()  //假设getComments()这个方法可以获取所有的评论
     };
   }
   componentDidMount() {
     // 订阅更改;监听  DataSource ,发生变化时更新数据
     DataSource.addChangeListener(this.handleChange);
   }
   componentWillUnmount() {
     // 清除订阅
     DataSource.removeChangeListener(this.handleChange);
   }
   handleChange() {
     // 当数据源更新时,更新组件状态
     this.setState({
       comments: DataSource.getComments()  //假设getComments()这个方法可以获取所有的评论
     });
   }
   render() {
     return (
       <div>
         {this.state.comments.map((comment) => (
           <Comment comment={comment} key={comment.id} />
         ))}
       </div>
     );
   }
 }
 // 假设 DataSource:来自外部;它自身有很多方法,如:getComments(),addChangeListener,removeChangeListener 等
//  假设 <Comment /> 是子组件,父组件 CommentList 需要将 comment 、key 传递给它

假设有个 订阅单个博客帖子的组件BlogPost,与上面的模式类似:

class BlogPost extends React.Component {
 constructor(props) {
   super(props);
   this.handleChange = this.handleChange.bind(this);
   this.state = {
     blogPost: DataSource.getBlogPost(props.id)
   };
 }
 componentDidMount() {
   DataSource.addChangeListener(this.handleChange);
 }
 componentWillUnmount() {
   DataSource.removeChangeListener(this.handleChange);
 }
 handleChange() {
   this.setState({
     blogPost: DataSource.getBlogPost(this.props.id)
   });
 }
 render() {
   return <TextBlock text={this.state.blogPost} />;
 }
}

以上两个组件的不同点

  • 调用方法不用

以上两个组件的相同点

  • 在挂载时,向 DataSource 添加一个更改侦 听器在侦 听器
  • 内部,当数据源发生变化时,调用 setState
  • 在卸载时,删除侦 听器

上面两个组件相同点的地方被不断的重复调用,在大型项目中,所以我们需要将这些共同使用的地方给抽象出来,然后让许多组件之间共享它,这正是高阶组件擅长的地方。

编写一个创建组件函数,这个函数接收两个参数,一个是要被包装的子组件,另一个则是该子组件订阅数据的函数。

 const CommentListWithSubscription = withSubscription(
    CommentList,
    (DataSource) => DataSource.getComments()
  );
  const BlogPostWithSubscription = withSubscription(
    BlogPost,
    (DataSource, props) => DataSource.getBlogPost(props.id)
  );
//以上写法相当于高级组件的调用,withSubscription为自定义的高阶组件;CommentList:被包装的子组件;CommentListWithSubscription:返回的包装后的组件

当渲染 CommentListWithSubscription 和 BlogPostWithSubscription 时, CommentList 和 BlogPost 将传递一个 data prop,其中包含从 DataSource 检索到的最新数据

 // 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {
 // ...并返回另一个组件...
 return class extends React.Component {
   constructor(props) {
     super(props);
     this.handleChange = this.handleChange.bind(this);
     this.state = {
       data: selectData(DataSource, props)
     };
   }
   componentDidMount() {
     // ...负责订阅相关的操作...
     DataSource.addChangeListener(this.handleChange);
   }
   componentWillUnmount() {
     DataSource.removeChangeListener(this.handleChange);
   }
   handleChange() {
     this.setState({
       data: selectData(DataSource, this.props)
     });
   }
   render() {
     // ... 并使用新数据渲染被包装的组件!
     // 请注意,我们可能还会传递其他属性
     return <WrappedComponent data={this.state.data} {...this.props} />;
   }
 };
}

HOC不会修改传入的组件,也不会使用继承来复制其行为,相反HOC是通过将组件包装在容器组件中来组成新的组件,HOC是纯函数,没有副作用

  • 被包装组件接收来自容器组件的所有prop,同时也接收一个新的用于render的data prop
  • HOC不用关心数据的使用方式,被包装组件也不用关心数据是怎么来的

不用改变原始组件使用组合

不要试图在 HOC 中修改组件原型(或以其他方式改变它)

function logProps(InputComponent) {
 InputComponent.prototype.componentDidUpdate = function(prevProps) {
   console.log('Current props: ', this.props);
   console.log('Previous props: ', prevProps);
 };
 // 返回原始的 input 组件,暗示它已经被修改。
 return InputComponent;
}
// 每次调用 logProps 时,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent)
//上面这种写法会造成另一个同样会修改componentDidUpate的HOC增强它,那么前面的HOC就会失效。

HOC不应该修改传入组件,而应该使用组合的方式,将组件包装在容器组件中实现功能。

function logProps(WrappedComponent) {
    return class extends React.Component {
      componentDidUpdate(prevProps) {
        console.log('Current props: ', this.props);
        console.log('Previous props: ', prevProps);
      }
      render() {
        // 将 input 组件包装在容器中,而不对其进行修改。Good!
        return <WrappedComponent {...this.props} />;
      }
    }
  }

约定-将不相关的 props 传递给被包裹的组件

HOC为组件添加特性,自身不应该大幅改变约定,HOC应该透传与自身无关的props,大多数HOC都应该包含一个类似于下面的render方法

render() {
  // 过滤掉非此 HOC 额外的 props,且不要进行透传
  const { extraProp, ...passThroughProps } = this.props;
  // 将 props 注入到被包装的组件中。
  // 通常为 state 的值或者实例方法。
  const injectedProp = someStateOrInstanceMethod;
  // 将 props 传递给被包装组件
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}	

约定-最大化可组合性

有时候它仅接受一个参数,也就是被包裹的组件:

const NavbarWithRouter = withRouter(Navbar);

HOC通常也可以接收多个参数

const CommentWithRelay = Relay.createContainer(Comment, config);

常见的HOC签名(React Redux的connect函数):

// React Redux 的 `connect` 函数const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

拆分connect函数

  // connect 是一个函数,它的返回值为另外一个函数。
  const enhance = connect(commentListSelector, commentListActions)
  // 返回值为 HOC,它会返回已经连接 Redux store 的组件
 const ConnectedComment = enhance(CommentList);

约定-包装显示名称以便轻松调试

HOC创建的容器组件会和任何其他组件一样,显示在React Developer Tools中,为了方便调试,需要选择显示一个名称,以表明他是HOC的产物

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

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return <EnhancedComponent />;
}

务必复制静态方法

   // 定义静态函数
 WrappedComponent.staticMethod = function() {/*...*/}
 // 现在使用 HOC
 const EnhancedComponent = enhance(WrappedComponent);
 // 增强组件没有 staticMethod
 typeof EnhancedComponent.staticMethod === 'undefined' // true
//为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上:
function enhance(WrappedComponent) {
   class Enhance extends React.Component {/*...*/}
   // 必须准确知道应该拷贝哪些方法 :(
   Enhance.staticMethod = WrappedComponent.staticMethod;
   return Enhance
 }

Refs 不会被传递

虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。

到此这篇关于React高阶组件使用教程详解的文章就介绍到这了,更多相关React高阶组件内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://blog.csdn.net/xbczrz/article/details/128111357

[!--infotagslink--]

相关文章

  • 关于React Native报Cannot initialize a parameter of type'NSArray<id<RCTBridgeModule>>错误(解决方案)

    这篇文章主要介绍了关于React Native报Cannot initialize a parameter of type'NSArray<id<RCTBridgeModule>>错误,本文给大家分享解决方案,需要的朋友可以参考下...2021-05-12
  • React引入antd-mobile+postcss搭建移动端

    本文给大家分享React引入antd-mobile+postcss搭建移动端的详细流程,文末给大家分享我的一些经验记录使用antd-mobile时发现我之前配置的postcss失效了,防止大家踩坑,特此把解决方案分享到脚本之家平台,需要的朋友参考下吧...2021-06-21
  • React使用高德地图的实现示例(react-amap)

    这篇文章主要介绍了React使用高德地图的实现示例(react-amap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-18
  • 使用 React 和 Threejs 创建一个VR全景项目的过程详解

    这篇文章主要介绍了使用 React 和 Threejs 创建一个VR全景项目的过程详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-06
  • React+高德地图实时获取经纬度,定位地址

    思路其实没有那么复杂,把地图想成一个盒子容器,地图中心点想成盒子中心点;扎点在【地图中心点】不会动,当移动地图时,去获取【地图中心点】经纬度,设置某个位置的时候,将经纬度设置为【地图中心点】即可...2021-06-20
  • React列表栏及购物车组件使用详解

    这篇文章主要为大家详细介绍了React列表栏及购物车组件使用,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-06-28
  • react使用antd表单赋值,用于修改弹框的操作

    这篇文章主要介绍了react使用antd表单赋值,用于修改弹框的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-29
  • React Native 启动流程详细解析

    这篇文章主要介绍了React Native 启动流程简析,文以 react-native-cli 创建的示例工程(安卓部分)为例,给大家分析 React Native 的启动流程,需要的朋友可以参考下...2021-08-18
  • 一百多行代码实现react拖拽hooks

    这篇文章主要介绍了一百多行代码实现react拖拽hooks,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-21
  • 示例详解react中useState的用法

    useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state,接下来通过一个示例来看看怎么使用 useState吧...2021-06-04
  • React 高阶组件HOC用法归纳

    高阶组件就是接受一个组件作为参数并返回一个新组件(功能增强的组件)的函数。这里需要注意高阶组件是一个函数,并不是组件,这一点一定要注意,本文给大家分享React 高阶组件HOC使用小结,一起看看吧...2021-06-13
  • React中使用setInterval函数的实例

    这篇文章主要介绍了React中使用setInterval函数的实例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-06
  • react为什么不推荐使用index作为key

    本文主要介绍了react为什么不推荐使用index作为key,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-07-22
  • 关于antd tree和父子组件之间的传值问题(react 总结)

    这篇文章主要介绍了关于antd tree 和父子组件之间的传值问题,是小编给大家总结的一些react知识点,本文通过一个项目需求实例代码详解给大家介绍的非常详细,需要的朋友可以参考下...2021-06-02
  • React Class组件生命周期及执行顺序

    这篇文章主要介绍了React Class组件生命周期,包括react组件的两种定义方式和class组件不同阶段生命周期函数执行顺序,本文给大家介绍的非常详细,需要的朋友可以参考下...2021-08-14
  • react之组件通信详解

    本篇文章主要介绍了React组件通信详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-10-15
  • 详解React中组件之间通信的方式

    这篇文章主要介绍了React中组件之间通信的方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-07-27
  • react自动化构建路由的实现

    这篇文章主要介绍了react自动化构建路由的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-23
  • react+ts实现简单jira项目的最佳实践记录

    这篇文章主要介绍了react+ts实现简单jira项目,本文通过图文实例相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-07-30
  • React 并发功能体验(前端的并发模式)

    React 是由 Facebook 软件工程师 Jordan Walke 创建,React 的第一个版本在七年前问世,现在,Facebook 负责维护,本文给大家介绍React 并发功能体验前端并发模式的问题,感兴趣的朋友跟随小编一起看看吧...2021-07-02