看題目感覺好高級的樣子,千萬不要被名字嚇到,它一點(diǎn)都不高深。
按照慣例先上圖,這一章的概覽:

1.從高階函數(shù)說起
維基百科對高階函數(shù)的定義:
在數(shù)學(xué)和計算機(jī)科學(xué)中,高階函數(shù)是至少滿足下列一個條件的函數(shù):
- 接受一個或多個函數(shù)作為輸入
- 輸出一個函數(shù)
是不是很簡單?滿足任一條件一句話說就是接受或者返回一個函數(shù)的函數(shù)就是高階函數(shù)。
舉個例子
funA(){
return funB(){};
}
funA 就是一個高階函數(shù)。
順便提一下圖上的高階函數(shù)對應(yīng)的三個實現(xiàn),也是面試經(jīng)常問道的,這里簡單說一下。
1).函數(shù)回調(diào)
我們開發(fā)最長用的封裝網(wǎng)絡(luò)請求是一個高階函數(shù)。
function funRequest(params){
return Http.post(params.URL,params.data);// 這里實際返回的是一個promise
}
2).柯里化
是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)
其實就是 fun(a,b,c) -------柯里化-----> fun(a)(b)(c);
舉個例子:
function add(a,b,c) {
return (a + b + c);// 返回三個參數(shù)的和
}
// 把上述函數(shù)柯里化(這是一個簡單實現(xiàn),有興趣的想想怎么實現(xiàn)任意個數(shù)的求和)
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).函數(shù)節(jié)流
這個也不高深,就是平時我們頁面加載過程中會有上拉頻繁,加載數(shù)據(jù)會出現(xiàn)之前的請求還沒有返回就又加載新的內(nèi)容,不停的發(fā)送請求。這時我們就會進(jìn)行節(jié)流。
偽代碼:
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('觸發(fā)請求'){
startRequest({URL:'sslslssllsl',data:{}});
}
2.高階組件(order-hight-component)
高階組件跟高階函數(shù)極度相似,把函數(shù)的傳入值和返回值從函數(shù)變成組件,那么這個函數(shù)就是高階組件。
WrapperComponent參數(shù)是一個組件,那么下面函數(shù)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>
)
}
}
上面就是最簡單的創(chuàng)建的兩個高階組件,功能是一樣的在元組件的頂上加上“HOC”。
- 繼承Component的是屬于屬性代理方式創(chuàng)建的高階組件;
- 繼承傳入組件的屬于反向繼承方式創(chuàng)建的高階組件。
1).屬性代理
屬性代理顧名思義,就是替代的意思。高階組件替?zhèn)魅虢M件管理控制props里面一切屬性,管理控制包括增,刪,改,查。同時他自身還有自身的狀態(tài),即state,來強(qiáng)化傳入組件。打個比方,傳入組件是畫一個圓,其中只有一個props屬性半徑radius。那我們在高階組件中就可以隨意操作這個屬性值,可大可小,還可以為props增加新的屬性,比如增加一個color屬性,表示圓的顏色;在組件外層加一個背景,美化傳入組件。這個比方由大家去實現(xiàn)。
這里舉一個稍微比這個難一點(diǎn)點(diǎn)的例子,受控的輸入框。輸入框組件的要求很簡單,就是輸入框中一直要有“輸入:”這兩個字和冒號。
分析:根據(jù)什么是受控組件,第一我們要通過state控制input的value屬性。
第二我們要監(jiān)控input輸入值得變化,每當(dāng)變化是我們拿到最新輸入值,然后在前面拼接上“輸入:”,設(shè)置一個state就可以了。所以需要onChange監(jiān)聽。
組件代碼如下:
class ControlInput extends Component{
// 一個受控組件,通過屬性代理的方式,把控制邏輯放進(jìn)高階組件中。
render(){
const { value , eventOnChange} = this.props;
return (
<input value={value} {...eventOnChange}/>
)
}
}
注意這里沒有任何邏輯,屬性代理嘛,邏輯當(dāng)然都在高階組件中啦。
高階組件如下:
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();
// 這句最直觀的體現(xiàn)什么是受控(要什么值顯示什么值)
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監(jiān)聽,方法在高階組件內(nèi)
},
}
const props = Object.assign({},this.props,newProps);// 合成最新的props傳給傳入組件
return (
<BaseComponent {...props}/>
)
}
}
export default AttributeAgentHigherOrderComponent2;
怎么用的呢,在導(dǎo)出ControlInput組件的地方調(diào)用即可
export default AttributeAgentHigherOrderComponent2(ControlInput);
也可以使用ES6注解方式:

2).反向繼承
屬性代理方式在高階組件中返回的組件繼承的是Component,而反向繼承則是繼承的傳入組件,根據(jù)繼承的特性,繼承可獲取父類的所有靜態(tài)資源,非私有屬性和方法,且根據(jù)情況可對原方法進(jìn)行重寫。所以反向繼承的方式也可以操作傳入組件的props以及state。還有一個更重的就是反向繼承可以進(jìn)行渲染劫持。
我們來句一個簡單的例子,還是一個輸入框,要求
輸入框中有值時就出現(xiàn)提交按鈕,沒有值時則消失。
-
提交按鈕可用
按照組件開發(fā),不用高階組件完全可以寫,還很簡單。但是現(xiàn)在我們就用高階組件寫,看有什么區(qū)別。先寫傳入組件: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> ) } }
上面就是將要被繼承的組件,里面有方法,但是卻沒有方法實體。這就是反向繼承可以在高階組件中進(jìn)行方法的重寫。注意組件中是有提交按鈕的,我們要在高階組件中進(jìn)行控制顯示和隱藏,使用的就是渲染劫持。
const ReverseInherit1 = BaseComponent =>
class extends BaseComponent{ // 繼承傳入組件
// 在這里定義監(jiān)聽value值變化的函數(shù)
valueChange = (eve) => {
console.log(eve.target.value);
this.setState({value:eve.target.value})
}
// 在這里重寫提交的函數(shù)
toSubmit = () => {
alert(`您要提交的值是:${this.state.value}`);
}
render(){
const { value } = this.state;
const superEle = super.render();// 拿到父組件的要渲染的結(jié)構(gòu)對象,做渲染劫持的關(guān)鍵
const newElement = React.cloneElement(superEle,this.props,superEle.props.children);
if(value){// 如果value有值就不做任何處理返回父組件的render
return (
super.render()
)
}else{// value 有值則對原來的結(jié)構(gòu)進(jìn)行調(diào)整
newElement.props.children.splice(1,1);
return (newElement)
}
console.log(superEle);
}
}
用法跟上一個屬性代理的一致。
是不是感覺高階組件也不過如此?實際上難就難在去抽象組件邏輯,針對不同的需求。我門項目上就有一個問題比如權(quán)限相關(guān)的,所有組件中都有于某一權(quán)限相關(guān)的按鈕或者其他內(nèi)容,這些按鈕和內(nèi)容是和權(quán)限相關(guān)的,權(quán)限不足的人不能看到,如果每個人寫組件的時候都在自己組件里判斷,然后進(jìn)行是否顯示,這就造成一個問題,如果以后權(quán)限變動了,就要改很多處,這是比較麻煩的。但是高階組件很好的解決了這個問題。只要在你寫的組件中進(jìn)行權(quán)限控制顯示的內(nèi)容上加一個標(biāo)記,比如ref=‘xxxPower’等。我就可以讓你組件通過我的高階組件時對你的加了權(quán)限標(biāo)記的內(nèi)容進(jìn)行顯示和隱藏,所有組件都可以。
這個例子寫在下面的工程源碼里面了。運(yùn)行后就是菜單 “HOC-反向繼承2.1”.源碼位置在powerButton文件夾。
工程源碼地址,點(diǎn)擊這里