React 精要面試題講解(五) 高階元件真解
說明與目錄
在學習本章內容之前,最好是具備react中‘插槽(children)’及‘組合與繼承’ 這兩點的知識積累。 詳情請參照React 精要面試題講解(四) 組合與繼承不得不說的祕密。 哦不好意思忘記了,四還沒寫呢。==!回頭補上。 __首先,我們要知道高階元件能夠做到什麼:對複用UI、資料邏輯等進行封裝,對引數元件進行制式處理,從而讓引數組建具備特定的ui或功能__ 那麼本節的學習目錄:
- 高階函式的認知
- 類的修飾器(decorator)的認知(類比高階函式)
- 高階元件的認知(類比修飾器)
- 高階元件的兩種形式(類比插槽)
- 高階元件之組合(代理)型別的高階元件
- 高階元件之繼承型別的高階元件
-
高階元件的認知總結
必須深刻認知,以上內容是循序漸進且相互關聯的關係,按照流程,我們將徹底把高階元件安排的明明白白,玩個透徹。
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 functiondecF(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); // 用法2decorator 修飾器寫法 @hoc class A extends React.Component{ //... }
大家有木有很眼熟啊 ?
ok, 我們寫一個常規的高階元件並暴露出去;
export defaultWrapedComponent=> classNewComponent extends React.Component{ render(){ return <WrapedComponenta = '添加了一個屬性' /> } }
這種箭頭函式的寫法好理解吧。
如程式碼所示,我們寫了一個高階元件,返回的新元件 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' constuseB =(<Provider > <ComAa='使用Provider時添加了a屬性'/> </Provider >)
// 寫一個組合形式的高階元件 const hocAddA = WrapedComponent => class NewComponent extends React.component{ render(){ return ( <div> <WrapedComponenta ='定義hocAddA時添加了a屬性'/> </div> ) } } // 我們來嘗試使用它 const NewA=hocAddA(ComA); // 或者 @hocAddA class ComB ...
ok, 上述兩種程式碼在使用後的表現幾乎是一致的(同樣實現了給ComA新增屬性a的功能)。
但是注意,插槽(children)的實現,不關心插槽元件的功能變化。只是把插槽當作當前元件的子元件去使用(這就是組合)。
//同樣的,我現在這樣使用 import ComA from'./ComA' constuseB =(<Provider > <ComAb='使用Provider時添加了b屬性'/> </Provider >)
而高階函式,在定義時就寫死了引數元件的功能變化。
傳入元件,得出的元件只會新增屬性a。
當然,我們也可以通過二階高階函式實現 用引數控制引數元件的功能變化:
定義一個二階高階元件 const hocAddProps =props => WrapedComponent => class NewComponent extends React.Component{ render(){ return ( <div> <WrapedComponent{...props}/> </div> ) } } //於是我們這樣使用它 constpropsAdded = { a: '添加了一個屬性a', b: ‘添加了一個屬性b' } constNewA = hocAddProps(propsAdded)(ComA)
諸如此類。實際上組合形式的高階元件能做到的事,用children基本都能做到。
那麼組合形式的高階元件和繼承形式的高階元件的區別在哪呢?
組合形式(也稱之為代理形式): 返回的新元件,繼承的還是React.Component,只是把引數元件作為新元件的子元件去使用,能夠實現給引數元件進行包裝、屬性的增刪改、狀態抽離等功能.
繼承形式: 返回的新元件,繼承的是 引數元件 ,從而實現以引數元件為模版,改寫引數元件的功能。
上述劃重點,要考。
我們再回過頭來思考類的修飾器——返回一個新的類或改寫引數類。
是不是一樣的道理啊。
所以說高階元件啥的,還是js啊,最多加了jsx的語法嘛。
5. 高階元件之組合(代理)型別的高階元件
上述我們已經知道了組合(代理)型別的高階元件的概念和思想,以及它能實現的功能。
那麼我們上demo程式碼
import React,{Component,createRef} from'react'; export defaulttitle=>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訪問ref4包裝元件 <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 defaultcolor=>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學習有幫助,記得點個關注,等待更新哦。