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)
打印结果如下:
React 精要面试题讲解(五) 高阶组件真解
React 精要面试题讲解(五) 高阶组件真解

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

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

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

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

React 精要面试题讲解(五) 高阶组件真解

那么类的修饰器——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);
 

打印如下:
React 精要面试题讲解(五) 高阶组件真解

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

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学习有帮助,记得点个关注,等待更新哦。

给TA打赏
共{{data.count}}人
人已打赏
随笔日记

EIGRP 高级实验

2020-11-9 4:04:37

随笔日记

C#闭包

2020-11-9 4:04:39

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索