一、什么是高階函數(組件),作用是什么?
子類使用父類的方法可以通過繼承的方式實現,那無關聯組件通信(redux)、父類使用子類方法(反向繼承)呢
為了解決類(函數)功能交叉/功能復用等問題,通過傳入類/函數返回類/函數(繼承)的方式使得類擁有自身未定義的方法。
例如react-redux
的connect方法使用了高階組件:
React Redux
的connect:
const HOC = connnect(mapStateToProps)(Comp);
// connect為柯里化函數 實際為 =>
function connect(mapStateToProps) {
// ...
return function(Comp) {
// ...
}
}
// 使用箭頭函數則為
const connect = mapStateToProps => Comp => {...};
二、通過高階函數實現兩個無關函數的通信
需求介紹
存在一個類SubClass
(子類),該類范圍內有數據state
對象,且有setState和getState
兩個函數方法。現在希望通過SupClass1
(超/父類1)去調用SubClass
(子類)的setState
方法,並在SupClass2
(超/父類2)里通過getState
方法輸出結果。
注意,子為sub,父為sup
文件目錄
├ ├── SubClass.js # 子類
├ ├── SupClass1.js # 父類1
├ ├── SupClass2.js # 父類2
├── index.html
給SubClass
類增加數據state,並賦予查詢和修改的能力
// SubClass.js
class SubClass {
constructor(args = {}) {
this.state = {
...args,
};
}
// 賦值時需要提供鍵和值
setState = (key, val) => {
this.state = {
[key]: val,
};
};
getState = (key) => {
if (key in this.state) {
return this.state[key];
}
// 當然我們希望嚴謹點
const err = '無效key值';
throw err;
};
}
我們試試SubClass
功能如何
// index.html
const subcls = new SubClass({name: 'xiaobe'});
const res = subCls.getState('name');
console.log('res', res);
// 輸出xiaobe,妥妥的
接下來我們給SupClass1
賦予setState
的能力
class SuperClass1 {
set(key, val) {
// SuperClass1里沒有setState方法!
this.setState(key, val);
}
}
如果直接執行類里的get
方法肯定是會出錯的。所以我們需要先對SupClass1
做點事情。
需要給SuperClass1
類里增加方法setState
,可以使用繼承
// SuperClass1.js
class SuperClass1 extends SubClass {
constructor(props) {
super(props);
}
set(key, val) {
// 父類1上使用子類的setState方法
this.setState(key, val);
}
}
// index.html
const supCls1 = new SuperClass1({name: 'sup-xiaobe'});
const res = supCls1.getState('name');
console.log(res);
// 也能輸出sup-xiaobe
但如果單純使用繼承的方式會造成很多的麻煩。例如子類和父類如果有同名方法,默認子類會覆蓋基類(父類的其他叫法)的同名方法,如果基類方法使用了函數綁定或箭頭函數,其this
的指向就改變了,指向了基類,導致自身同名方法失效。
因此我們還是需要通過高階組件實現;
首先我們先給子類SubClass
增加一個HOC入口
class SubClass {
// ...
HOC(cls) {
// 需要調用SubClass類的方法,所以需要存一份其this
const sub_this = this;
// 父類除了以下新增的兩個方法,其他無任何變化地返回!
return class extends cls {
constructor(props) {
super(props);
// 此處this指向該子類,sub_this指向SubClass類
this.getState = sub_this.getState;
this.setState = sub_this.setState;
}
}
}
// ...
}
接着我們來父類1SupClass1
實例化前升級(調用HOC)!
// index.html
const subCls = new SubClass();
// 在子類實例化后給父類加上HOC方法
const supClsHoc1 = subCls.HOC(SuperClass1);
// 實例化父類
const supCls1 = new supClsHoc1();
// 重新定義state.name
supCls1.set('name', 'sup-xiaobe');
console.log(supCls.getState('name'));
// 輸出sup-xiaobe
同理地完成SupClass2
// SupClass2.js
class SuperClass2 {
get(key) {
return this.getState(key);
}
}
// 最終的index.html
const subCls = new SubClass({name: 'xiaobe'});
const supClsHoc1 = subCls.HOC(SuperClass1);
const supClsHoc2 = subCls.HOC(SuperClass2);
const supCls1 = new supClsHoc1();
const supCls2 = new supClsHoc2();
supCls1.set('name', 'sup-xiaobe');
const res = supCls2.get('name');
console.log('res', res);
這么一個基礎簡單的組件通信就完成了。
根據這個思路可以封裝一個類似全局變量的Store.js
思考個問題🤔
getState和setState方法里使用的this指向?
我先把SubClass
的完整代碼列下來
class SubClass {
constructor(args = {}) {
this.state = {
...args,
};
}
// 使用了箭頭函數!
setState = (key, val) => {
this.state = {
[key]: val,
};
};
// 使用了箭頭函數!
getState = (key) => {
if (key in this.state) {
return this.state[key];
}
return "";
};
HOC(cls) {
const sub_this = this;
return class extends cls {
constructor(props) {
super(props);
// 此處this指向該子類,sub_this指向SubClass類
this.getState = sub_this.getState;
this.setState = sub_this.setState;
}
}
}
}
可以發現凡是在方法內使用了類里的數據我都使用了箭頭函數,我們想想,如果不使用箭頭函數this
指向會出現什么問題?
getState(key) {
// ...
}
我們執行
const subCls = new SubClass({name: 'xiaobe'});
const supClsHoc2 = subCls.HOC(SuperClass2);
const supCls2 = new supClsHoc2();
console.log('supCls2', supCls2.get('name'));
會發現瀏覽器直接給報錯:
Cannot use 'in' operator to search for 'name' in undefined
因為在SuperClass2
類里根本不存在state
!換句話說即this.state = undefined
,在undefined
里使用對象方法,當然出錯。
找到問題所在(我們需要的數據/方法在SubClass
類里),我們有以下幾種解決方法:
// 1.在HOC方法里傳入state
this.state = sub_this.state;
// 2.getState使用箭頭函數,使得this永遠指向SubClass方法
getState = key => { ... };
// 3.將傳入的getState方法this綁定到SubClass類上
this.getState = sub_this.getState.bind(sub_this);
這么一來就解決了this
指向錯誤的問題,關於this
的全面解析,大家可以看我的這篇文章:
--- 后面介紹