高阶组件(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 (<WrappedComponentinjectedProp={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 !== EnhancedComponent2const EnhancedComponent = enhance(MyComponent);// 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!return <EnhancedComponent />;
    }
    
  • 务必复制静态方法
       // 定义静态函数WrappedComponent.staticMethod = function() {/*...*/}// 现在使用 HOCconst EnhancedComponent = enhance(WrappedComponent);// 增强组件没有 staticMethodtypeof 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(精读官方文档) - 高级指引 -高阶组件相关推荐

  1. react router官方文档_阿里开源可插拔 React 跨端框架 UmiJS

    点击上方"开发者技术前线",选择"星标" 18:30 在看 真爱 作者:Tamic  |  编辑: 可可 阿里之前开源:阿里闲鱼开源 Flutter 应用框架 ...

  2. Unity3D 官方文档 UGUI总览 可互动组件的介绍

    版本:unity 5.6  语言:C# 总起: 可互动组件包括按钮.复选框.滑块.滚动条等,本身它们是不可见的,但它们内部有可视化组件. 做UI的时候,如果不需要什么特效本身使用onClick.Add ...

  3. 再仔细读读react18官方文档吧 20220608

    章节回顾: 再仔细读读react18官方文档吧 20220602 目录 卸载组件api 引入ReactDOM方式变了 root的渲染方式变了 什么是单页面应用? 传递事件函数的多种写法 React中的 ...

  4. 微信小程序多选复选框checkbox。微信小程序官方文档bug

    由于个人从事微信小程序开发相关工作,在查询相关文档时(微信官方文档-小程序-表单组件-checkbox),发现示例代码中的一处错误. 问题出现原因:根据文档示例代码提示,设置本人本地代码,却发现下图红 ...

  5. React解密:React高阶组件是什么?

    React中比较高大上的东西,高阶组件应该算是一个点,开发中也许写的不多,但是我们必须要直到高阶组件是什么,高阶组件能干什么? A higher-order component is a functi ...

  6. react高阶组件和hooks

    1. react高阶组件 1.1 高阶组件的概念 高阶组件(Higher Order Component,简称:HOC ): 是 React 中用于重用组件逻辑的高级技术, 它本身不是react中的组 ...

  7. react高阶组件小坑

    在类组件中想要使用hook函数,使用高阶组件对类进行增强的时候发现高阶组件函数名大写就会报错 错误内容大概为:hook函数在return语句之后声明这是不被允许的,正确应该是在函数头部.回调函数之前声 ...

  8. 阅读React官方文档

    阅读React官方文档 1,组件&props 2,State&生命周期 3,事件处理 (1)事件绑定方法一: (2)事件绑定方法二: (3)事件绑定方法三: (4)向事件传递参数: 4 ...

  9. ExoPlayer详解——高级主题(官方文档)

    ExoPlayer详解系列文章 ExoPlayer详解--入门(官方文档) ExoPlayer详解--媒体类型(官方文档) ExoPlayer详解--高级主题(官方文档) 一.数字版权管理 ExoPl ...

最新文章

  1. java 隐藏参数,如何在没有JVM参数的情况下隐藏java 9中的“...
  2. Nature:将光计算与AI推理整合,实现高速高带宽低功耗AI计算
  3. Spring Cloud Alibaba - 24 Gateway-路由、断言(Predicate)、过滤器(Filter)初体验
  4. [转载]MVC、MVP以及Model2(下)
  5. Android中使用的数据单位
  6. z-index的学习整理转述
  7. HDU 6168 Numbers 思维
  8. 小白上手Mysql数据库指南~~
  9. 深度学习标注工具LabelImg的使用方法
  10. (5)DFS(深度优先搜索算法):排列数字
  11. 速成pytorch学习——6天Dataset和DataLoader
  12. java工具链 有什么_Iodine:一个优秀的Java语言工具链
  13. 金蝶kis记账王使用前要准备哪些资料
  14. android开发访问百度搜索,Android开发如何添加搜索功能———大神求救啊
  15. 联邦快递认了:转运华为货件到美国,但称是“失误”!
  16. 基于PID算法的房间温度控制及Python程序
  17. andriod studio 开发
  18. git 删除历史commit
  19. 【centos】安装wget------转发自【小姜dot】
  20. python协程爬取斗鱼美女图片

热门文章

  1. 国家职业资格证书等级说明
  2. Proteus8.9 VSM Studio WINAVR编译器仿真ATmega16系列a18_正反转可控步进电机
  3. 规定时间未完成考核,大量小米用户被取消 MIUI 内测资格
  4. win7 host文件的位置
  5. 什么是SIP广播系统
  6. 农商行计算机考英语,2018农村信用社(农商行)考试 计算机每日一练+答案
  7. 图片编辑软件有哪些?建议收藏这些软件
  8. JAVA选择合适的垃圾收集器+内存分配实战
  9. 爬虫:boss直聘自动投递简历+数据获取
  10. android6 接听电话时声音由默认听筒输出改为外放输出