zoukankan      html  css  js  c++  java
  • React 精要面试题讲解(五) 高阶组件真解

    说明与目录

     在学习本章内容之前,最好是具备react中‘插槽(children)’及‘组合与继承’ 这两点的知识积累。
     详情请参照React 精要面试题讲解(四) 组合与继承不得不说的秘密。
     哦不好意思忘记了,四还没写呢。==!回头补上。
      __首先,我们要知道高阶组件能够做到什么:  对复用UI、数据逻辑等进行封装,对参数组件进行制式处理,从而让参数组建具备特定的ui或功能__
     那么本节的学习目录: 
    
    1. 高阶函数的认知

    2. 类的修饰器(decorator)的认知(类比高阶函数)

    3. 高阶组件的认知(类比修饰器)

    4. 高阶组件的两种形式(类比插槽)

    5. 高阶组件之组合(代理)类型的高阶组件

    6. 高阶组件之继承类型的高阶组件

    7. 高阶组件的认知总结

      必须深刻认知,以上内容是循序渐进且相互关联的关系,按照流程,我们将彻底把高阶组件安排的明明白白,玩个透彻。

    1. 高阶函数的认知

    在本系列之前的学习中,你应当明白——组件的本质是函数。
    那么什么是高阶函数? 
    高阶函数也是一个函数,__它接受函数为参数,返回处理过后的函数或者一个新函数__。
    那么我们知道,高阶函数的作用是对参数函数进行统一的加工处理。
    形如:
    
        //这里我们写了一个函数作为之后的参数
        //思考一下为什么把参数函数定义在上面
        function testedFunc(){
              this.a = 1;
        }
    
        // 在此定义一个高阶函数
        function highOrderFunc(func){
            func.prototype.aaa = 3;
            return func;    
        }
    
        //这里测试一下我们写的高阶函数
       var newFunc =  highOrderFunc(testedFunc);
      
       //打印看看处理后的函数原型及实例
       console.log(newFunc.prototype,1);
       console.log(new newFunc(),2);
       
    

    (别那么懒,赶紧复制粘贴f12)
    打印结果如下:

    那么我们知道了,高阶函数 作用是 处理(加工)传入的函数,以达成某种目的…
    

    2. 类的修饰器(decorator)的认知

    ES6增添了class类,我们知道,类的本质也是函数。

         class testClass{
           a = 1;
        }
         console.log( typeof testClass)
    
    打印结果如下:
    

    那么类的修饰器——decorator 是个怎样的东西咧?
    __类的修饰器是es6的提案之一,在es7标准中实现。 修饰器也是一个函数,把传入的原有类修饰一下,return 出处理后的类或新类。__
    这时候我们脑海中应该闪过一个词——高阶类…(???黑人问号)
    不觉得太难听了吗? 
    

    ok,我们还是以代码来演示一下:

        //注意,下面这种@xx的写法是修饰器的标准写法,是属于es7的产物,要用babel哦~
    
    
        //定义一个修饰器decF
        function  decF(adornedClass){
            return class extends adornedClass{
                    b= 2
            }
        }
     
    // 使用方式1 : @decF
       @decF
       class foo{
           a =  1 
       }
     console.log( new foo(),1)
    
     class bar{
        a='bar'
     }
    // 使用方式2 : decF(); 
     const newBar =  decF(bar);
     console.log( new newBar(),2);
     
    
    

    打印如下:

    瞧啊,修饰器就是这么个东西。
    要注意的是,类的修饰器能否修饰函数?为什么?
    // 可以自己去找答案,再讲别的就跑题了。


    3. 高阶组件的认知(类比修饰器)

    那么经过类的修饰器的认知,高阶组件的概念就很明朗了。
    React高阶组件(high-order-component,简称hoc)就是一个类的修饰器啊…它接受一个组件类为参数,返回出一个新的组件类;
    形如:

       // 用法1 高阶函数式写法 
        hoc(WrapedComponent);
       // 用法2  decorator 修饰器写法 
       @hoc
       class A extends React.Component{
          //...
      }
    

    大家有木有很眼熟啊 ?

    ok, 我们写一个常规的高阶组件并暴露出去;

       export default  WrapedComponent  => class  NewComponent extends React.Component{
    
             render(){
               return <WrapedComponent  a = '添加了一个属性' />
             }
        }
    
    

    这种箭头函数的写法好理解吧。
    如代码所示,我们写了一个高阶组件,返回的新组件 NewComponent里,用组合的形式使用了传入的组件WrapedComponent。(所以不明白组合与继承的童鞋,赶紧补一补再来看啊)
    这里有人问 connect为啥两个括号啊

     //形如
     connect(func1,func2 )( WrapedComponent)   
    

    OK ,我们也手写一下它。

         export default (func1,func2)=>WrapedComponent=>class NewComponent extends React.Component{
            // ....
       }
    

    这个两层箭头函数好理解吧?

    顺便说下,一般像connect这样多嵌套了一层的高阶函数,我称之为二阶高阶函数。此后类推,三个括号就叫三阶高阶函数…


    4. 高阶组件的两种形式(类比插槽)

    同children 一样,高阶组件也存在组合(也可称之为代理)和继承两种形式。
    那么高阶组件和children插槽有什么关系呢?
    我们来类比以下代码:

      //写一个组合形式的具备插槽功能的组件Provider 
       class Provider extends React.Component{
            render(){
                return (
                    <div>
                        {this.props.children}
                    </div>
                )
            }
       }
      // 下面是使用的方式
        import ComA from  './ComA'
        const  useB =  (<Provider > <ComA  a='使用Provider时添加了a属性'/> </Provider >)
    
    
       // 写一个组合形式的高阶组件
       const hocAddA = WrapedComponent => class NewComponent extends React.component{
            render(){
                return (
                    <div>
                          <WrapedComponent  a ='定义hocAddA时添加了a属性'/>
                    </div>
                )
            }
        }
       // 我们来尝试使用它
       const NewA  =  hocAddA(ComA);
       // 或者
       @hocAddA 
       class ComB ...
    

    ok, 上述两种代码在使用后的表现几乎是一致的(同样实现了给ComA添加属性a的功能)。
    但是注意,插槽(children)的实现,不关心插槽组件的功能变化。只是把插槽当作当前组件的子组件去使用(这就是组合)。

       //同样的,我现在这样使用
        import ComA from  './ComA'
        const  useB =  (<Provider > <ComA  b='使用Provider时添加了b属性'/> </Provider >)
    

    而高阶函数,在定义时就写死了参数组件的功能变化。
    传入组件,得出的组件只会添加属性a。
    当然,我们也可以通过二阶高阶函数实现 用参数控制参数组件的功能变化:

      定义一个二阶高阶组件
       const hocAddProps =  props => WrapedComponent => class NewComponent extends React.Component{
            render(){
                return (
                    <div>
                          <WrapedComponent  {...props}/>
                    </div>
                )
            }
      }
      //  于是我们这样使用它
      const  propsAdded = { 
            a: '添加了一个属性a',
            b: ‘添加了一个属性b'
      } 
      const  NewA = hocAddProps(propsAdded)(ComA)
    

    诸如此类。实际上组合形式的高阶组件能做到的事,用children基本都能做到。

    那么组合形式的高阶组件和继承形式的高阶组件的区别在哪呢?
    组合形式(也称之为代理形式): 返回的新组件,继承的还是React.Component,只是把参数组件作为新组件的子组件去使用,能够实现给参数组件进行包装、属性的增删改、状态抽离等功能.
    继承形式: 返回的新组件,继承的是 参数组件 ,从而实现以参数组件为模版,改写参数组件的功能。
    上述划重点,要考。
    我们再回过头来思考类的修饰器——返回一个新的类或改写参数类。
    是不是一样的道理啊。
    所以说高阶组件啥的,还是js啊,最多加了jsx的语法嘛。

    5. 高阶组件之组合(代理)类型的高阶组件

    上述我们已经知道了组合(代理)类型的高阶组件的概念和思想,以及它能实现的功能。
    

    那么我们上demo代码

         
    import React,{Component,createRef} from  'react';
    export default  title=>WrapedComponent=> class NewComponent extends Component{
        //抽离状态
        state={
            value:''
        }
        // 访问refs 
        myref=createRef();
        handleInputChange=(e)=>{
            this.setState({
                value:e.target.value
            })
        }
    
        render(){
            const {wap,...otherprops} = this.props;
            const newProps = {
                value:this.state.value,
                onChange:this.handleInputChange
            }
            //包装组件
            return (
                <div>
                    我是组件NewComponent,是典型的代理形式的高阶组件,我除了做自己的事,还可以对 我的参数组件:
                      1增加/删减props 2抽离状态 3访问ref  4包装组件
                    <div>我的title:{title}</div>
                    <WrapedComponent {...otherprops} ref={this.myref} inputProps={newProps}/>
                </div>
            )
        }
    }
    

    这里要单独说一下上述功能中的状态抽离。
    状态抽离(状态提升): 把参数组件(即代理形式中使用的子组件)的状态提升到NewComponent(即代理形式中的当前组件,也就是父组件) 中,这样一来,子组件只负责UI渲染,而父组件通过props传递state实现数据的控制
    也就是说, NewComponent 成为参数组件的容器组件,参数组建单纯作为UI组件
    ps: 容器组件和UI组件的概念是相对的。 例如 把B的状态抽离到父组件A上,那么A相对于B来说是B的容器组件,要这么去理解。后续讲react-redux中会提到。


    6. 高阶组件之继承类型的高阶组件

    同样的,上述我们已经知道了 继承类型的高阶组件的概念和思想,那么我们也直接上demo代码

    import React from  'react'
    
    //这个是给返回的新组件起名用的函数,有兴趣可以结合调试器玩玩。
    function getDisplayName(WrapedComponent){
        return WrapedComponent.displayName||WrapedComponent.name||'component'
    }
    
    export default  color=>WrapedComponent=> class NewComponent extends WrapedComponent{
        // static displayName = `E(${getDisplayName(Inconponent)})`
    ;    
        aaa = '我改写了参数组件中的aaa属性'
        compoenentDidMount(){
            console.log('我不仅可以改写属性和方法,我还能改写钩子')
        }
        render(){
            const {wap,...otherprops} = this.props;
            const element = super.render();
            console.log(element);
            const newStyle = {
                color:element.type==='div'?color:null
            }
            const newProps = {
                ...otherprops,
                style:newStyle
            }
           // 我甚至还改写了参数组件的UI
            return React.cloneElement(element,newProps,element.props.children)
        }
    }
    

    如上述代码所示(跟着敲一下啊懒虫),我们成功做到了以参数组件为模版,改写了参数组件中已定义的属性、方法、钩子,甚至UI,增添了参数组件中未定义的属性、方法、钩子等。
    当然,同官方文档中 ‘组合和继承’ 这一章中的思想一致,绝大部分情况下,我们用不到继承类型的高阶组件,也不提倡这种形式的用法(其实我个人觉得挺好玩的)。


    7. 高阶组件的认知总结

    那么我们通过以上学习,已经完完整整掌握了高阶组件的使用。
    在日常项目中,我们也可以在合适的场景中使用高阶组件完成对应的需求。
    回顾最上面提到过的高阶组件的使用场景:
     __对复用UI、数据逻辑等进行封装,对参数组件进行制式处理,从而让参数组建具备特定的ui或功能__
    
    再回顾下上述讲到过的高阶函数,类的修饰器等——
    你get到了吗?
    
    面试中会问到高阶组件的问题,消化掉这一篇,那么你便可以连续不断的给面试官讲上半个小时征服他。
    

    最后,如果本章内容对你的react学习有帮助,记得点个关注,等待更新哦。

  • 相关阅读:
    Visual C# 2008+SQL Server 2005 数据库与网络开发 12.4 自定义控件
    Visual C# 2008+SQL Server 2005 数据库与网络开发 13.1 菜单和菜单事件的创建
    Visual C# 2008+SQL Server 2005 数据库与网络开发 11.6 小结
    vs2005部署报表服务器项目老提示输入用户名密码解决办法
    Excel 文本处理常用函数
    使用APMServ时出现“1、Apache启动失败,请检查相关配置。√MySQL5.1已启动。”错误的终极解决办法:
    <img> 标签的border等属性尽量少用
    时间相减
    全面介绍 Netstat命令的使用技巧!
    apmserver中 mysql的root密码设定
  • 原文地址:https://www.cnblogs.com/sanchang/p/10614074.html
Copyright © 2011-2022 走看看