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學習有幫助,記得點個關注,等待更新哦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM