R6- React高階元件詳解
看題目感覺好高階的樣子,千萬不要被名字嚇到,它一點都不高深。 按照慣例先上圖,這一章的概覽:
1.從高階函式說起
維基百科對高階函式的定義:
- 接受一個或多個函式作為輸入
- 輸出一個函式
是不是很簡單?滿足任一條件一句話說就是接受或者返回一個函式的函式就是高階函式。 舉個例子
funA(){
return funB(){};
}
funA 就是一個高階函式。 順便提一下圖上的高階函式對應的三個實現,也是面試經常問道的,這裡簡單說一下。
1).函式回撥
我們開發最長用的封裝網路請求是一個高階函式。
function funRequest(params){
return Http.post(params.URL,params.data);// 這裡實際返回的是一個promise
}
2).柯里化
是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數而且返回結果的新函式的技術
其實就是 fun(a,b,c) -------柯里化-----> fun(a)(b)©; 舉個例子:
function add(a,b,c) { return (a + b + c);// 返回三個引數的和 } // 把上述函式柯里化(這是一個簡單實現,有興趣的想想怎麼實現任意個數的求和) function addH(a) { return function (b) { return function (c) { return (a + b + c); } } } console.log(add(1,2,3)); console.log(addH(1)(2)(3));
3).函式節流
這個也不高深,就是平時我們頁面載入過程中會有上拉頻繁,載入資料會出現之前的請求還沒有返回就又載入新的內容,不停的傳送請求。這時我們就會進行節流。 虛擬碼:
function request() { let start = true; return function (params) { if(start) { start = false; Http.post(params.URL, params.data).then(res => { start = true; }) } } } let startRequest = request(); if('觸發請求'){ startRequest({URL:'sslslssllsl',data:{}}); }
2.高階元件(order-hight-component)
高階元件跟高階函式極度相似,把函式的傳入值和返回值從函式變成元件,那麼這個函式就是高階元件。 WrapperComponent引數是一個元件,那麼下面函式HocComp1和HocComp2都是高階元件
const HocComp1 = WrapperComponent =>
class extends Component{// 繼承Component
render(){
return (
<div>
HOC
<WrapperComponent />
</div>
)
}
}
const HocComp2 = WrapperComponent =>
class extends WrapperComponent{// 繼承的是傳入的元件
render(){
const oldElements = super.render();
return (
<div>
HOC
{oldElements}
</div>
)
}
}
上面就是最簡單的建立的兩個高階元件,功能是一樣的在元元件的頂上加上“HOC”。
- 繼承Component的是屬於屬性代理方式建立的高階元件;
- 繼承傳入元件的屬於反向繼承方式建立的高階元件。
1).屬性代理
屬性代理顧名思義,就是替代的意思。高階元件替傳入元件管理控制props裡面一切屬性,管理控制包括增,刪,改,查。同時他自身還有自身的狀態,即state,來強化傳入元件。打個比方,傳入元件是畫一個圓,其中只有一個props屬性半徑radius。那我們在高階元件中就可以隨意操作這個屬性值,可大可小,還可以為props增加新的屬性,比如增加一個color屬性,表示圓的顏色;在元件外層加一個背景,美化傳入元件。這個比方由大家去實現。 這裡舉一個稍微比這個難一點點的例子,受控的輸入框。輸入框元件的要求很簡單,就是輸入框中一直要有“輸入:”這兩個字和冒號。 分析:根據什麼是受控元件,第一我們要通過state控制input的value屬性。 第二我們要監控input輸入值得變化,每當變化是我們拿到最新輸入值,然後在前面拼接上“輸入:”,設定一個state就可以了。所以需要onChange監聽。 元件程式碼如下:
class ControlInput extends Component{
// 一個受控元件,通過屬性代理的方式,把控制邏輯放進高階元件中。
render(){
const { value , eventOnChange} = this.props;
return (
<input value={value} {...eventOnChange}/>
)
}
}
注意這裡沒有任何邏輯,屬性代理嘛,邏輯當然都在高階元件中啦。 高階元件如下:
import React,{ Component } from 'react';
import '../../style/higherOrderComponent/higherOrderComponent.scss';
const AttributeAgentHigherOrderComponent2 = (BaseComponent) =>
class extends Component{
constructor(props){
super(props);
this.state = {
value:this.props.initValue || '',
}
}
onValueChange = (event) => {
let value = event.target.value.toString();
// 這句最直觀的體現什麼是受控(要什麼值顯示什麼值)
value = `輸入:${value === '輸入' ? '' : value.replace('輸入:','')}`;
this.setState({value:value});
}
render(){
const { value } = this.state;
const newProps = {
value: value,// input 的value屬性
eventOnChange:{
onChange: this.onValueChange,// input的onChange監聽,方法在高階元件內
},
}
const props = Object.assign({},this.props,newProps);// 合成最新的props傳給傳入元件
return (
<BaseComponent {...props}/>
)
}
}
export default AttributeAgentHigherOrderComponent2;
怎麼用的呢,在匯出ControlInput元件的地方呼叫即可
export default AttributeAgentHigherOrderComponent2(ControlInput);
也可以使用ES6註解方式:
2).反向繼承
屬性代理方式在高階元件中返回的元件繼承的是Component,而反向繼承則是繼承的傳入元件,根據繼承的特性,繼承可獲取父類的所有靜態資源,非私有屬性和方法,且根據情況可對原方法進行重寫。所以反向繼承的方式也可以操作傳入元件的props以及state。還有一個更重的就是反向繼承可以進行渲染劫持。
我們來句一個簡單的例子,還是一個輸入框,要求
-
輸入框中有值時就出現提交按鈕,沒有值時則消失。
-
提交按鈕可用 按照元件開發,不用高階元件完全可以寫,還很簡單。但是現在我們就用高階元件寫,看有什麼區別。先寫傳入元件:
class ReverseInput extends Component{ constructor(props){ super(props); this.state = { value:'' } } // 處理提交動作。定義了方法沒有方法實體 toSubmit = () => {} // 處理輸入值變化動作。定義了方法沒有方法實體 valueChange = (eve) => {} render(){ const { value } = this.state; return ( <div> <input onChange={this.valueChange} value={value}/> <button onClick={this.toSubmit}>提交</button> </div> ) } }
上面就是將要被繼承的元件,裡面有方法,但是卻沒有方法實體。這就是反向繼承可以在高階元件中進行方法的重寫。注意元件中是有提交按鈕的,我們要在高階元件中進行控制顯示和隱藏,使用的就是渲染劫持。
const ReverseInherit1 = BaseComponent =>
class extends BaseComponent{ // 繼承傳入元件
// 在這裡定義監聽value值變化的函式
valueChange = (eve) => {
console.log(eve.target.value);
this.setState({value:eve.target.value})
}
// 在這裡重寫提交的函式
toSubmit = () => {
alert(`您要提交的值是:${this.state.value}`);
}
render(){
const { value } = this.state;
const superEle = super.render();// 拿到父元件的要渲染的結構物件,做渲染劫持的關鍵
const newElement = React.cloneElement(superEle,this.props,superEle.props.children);
if(value){// 如果value有值就不做任何處理返回父元件的render
return (
super.render()
)
}else{// value 有值則對原來的結構進行調整
newElement.props.children.splice(1,1);
return (newElement)
}
console.log(superEle);
}
}
用法跟上一個屬性代理的一致。
是不是感覺高階元件也不過如此?實際上難就難在去抽象元件邏輯,針對不同的需求。我門專案上就有一個問題比如許可權相關的,所有元件中都有於某一許可權相關的按鈕或者其他內容,這些按鈕和內容是和許可權相關的,許可權不足的人不能看到,如果每個人寫元件的時候都在自己元件裡判斷,然後進行是否顯示,這就造成一個問題,如果以後許可權變動了,就要改很多處,這是比較麻煩的。但是高階元件很好的解決了這個問題。只要在你寫的元件中進行許可權控制顯示的內容上加一個標記,比如ref=‘xxxPower’等。我就可以讓你元件通過我的高階元件時對你的加了許可權標記的內容進行顯示和隱藏,所有元件都可以。
這個例子寫在下面的工程原始碼裡面了。執行後就是選單 “HOC-反向繼承2.1”.原始碼位置在powerButton資料夾。 工程原始碼地址,點選這裡