React中 `鎖定`this的N種‘facade pattern’模式
由於本文是主要介紹React中鎖定this
的N種方法,不會過多的介紹this
多面性的原因,相信大家應該都知道詞法作用域
和動態作用域
。並且也知道在es6
之前我們依然有很多種方式,去鎖定this
的指向(call, apply, bind)。接下來我們也會結合這些方式,在React
中來鎖定this
;不過在這之前,我們先看下之前我們採取的方式。對於本文標題facade pattern
(外觀模式)指的是,這些鎖定this
的方式,只是看起來不一樣,有些本質上是一樣的,有些是es5通過一些技巧實現的
,有些是es6原生支援的(箭頭函式)
,這些看起來很不一樣的方式,有些在babel
編譯以後本質是一樣的(使用閉包鎖住上下文, 通過高階函式返回新的函式)。
千好萬好ES6
好(箭頭函式 () => {}好)
看下在es6之前我們是如何解決this
// 回撥裡面使用this var Demo = { init() { this.initEvents(); } validateData() { return true; } initEvents() { var self = this; $('a.submit').click(function () { self.validateData(); }); } } Demo.init(); // 借用其他物件的方法 var name = '影帝'; var Person = { name: '渣渣輝', sayName() { console.log(this.name); } }; var Other = { name: '張家輝' }; var sayName = Person.sayName; sayName(); // '影帝' sayName.call(Other); // 張家輝' Other.sayName = sayName; var otherSayName = Other.sayName; otherSayName(); // '影帝' Other.sayName.call(Person); '渣渣輝', 複製程式碼
這樣看起來好奇怪呀,但是沒辦法,我們早已經習慣,自從ES2015
稱為標準以來,我們已經很少看到這種程式碼了。好了言歸正題,我們開始看看React
中this
的問題。
React中this的問題
既然js
中有這些問題,當然react
也不能列外,使用react
的我們都知道,為了保持元件的高複用,元件可以分為容器元件
和UI元件
,容器元件
元件負責業務處理,UI元件
負責頁面渲染,一般情況下UI元件
都儘量要是純的
,沒有自己的狀態,也不處理業務,但是有時候需要觸發一些事件,這樣就需要執行從父元件等傳過來的函式,同時這些函式裡面一般還會出現this
,我們希望this
的指向是上層元件的引用,而這個時候函式的執行者卻不是上層元件,於是this
開始變臉,變得我們不認識。但是我們要避免這種情況,就需要鎖定this
,鎖定this
的方式有很多,我們一一分析,這其中各有優劣,也有react
推薦的最佳實踐
。至於如何選擇,看業務場景,以及團隊編碼風格,建議最好還是遵守最佳實踐
;
React中‘鎖定’this的N種方法
1.函式包裝模式
/** * 函式包裝模式 function wrapper pattern */ class Component extends React.Component { doSomething() { childDoSomething() {} render() { return ( <div onClick={() => this.doSomething();}> <ChildComponent doSomething={() => this.childDoSomething();} /> </div> ); } } 複製程式碼
建議:不推薦也不禁止。
優缺點:
- 缺點:沒有明顯的缺點,只是需要多包裹一層
- 優點:簡單,易於理解,對新手比較友好
實現原理:
這裡是通過es6
箭頭函式來實現this
的鎖定;
當然對應的有es5
版本,其實就是我們之前熟悉的那種方式,且看程式碼
/** * function wrapper pattern es5 */ class Component extends React.Component { doSomething() { childDoSomething() {} render() { const self = this; return ( <div onClick={function () { self.doSomething();}> <ChildComponent doSomething={function () { self.childDoSomething();} /> </div> ); } } 複製程式碼
建議: 不推薦, 最好不要這樣寫。
優缺點:
- 缺點:需要對this的指向進行儲存,導致程式碼沒有箭頭函式來的簡潔, 優雅(其實也就是箭頭函式的優點)
- 優點:對熟悉es5老式寫法的比較友好
實現原理:
使用變數先保持對this
的引用,使用的時候是這個變數,也就是此函式外部的this
;
2.渲染繫結模式
/** * 渲染繫結模式 render bind pattern * 或者叫 * 懶繫結模式 lazy bind pattern */ class Component extends React.Component { doSomething() {} childDoSomething() {} render() { return ( <div onClick={this.doSomething.bind(this)}> <ChildComponent doSomething={this.childDoSomething.bind(this);} /> </div> ); } } 複製程式碼
建議: 禁止採取這種模式
優缺點:
render constructor
實現原理:
就是使用bing
來鎖定
,關於bind
的使用以及原理可以參考mdn
或者網上其他文章或者教程,當然你也可以實現自己的bind
;
3.覆寫繫結模式
/** * 覆寫繫結模式rewrite bind pattern * 或者叫 * 預繫結模式 prepare bind pattern */ class Component extends React.Component { constructor() { this.doSomething = this.doSomething.bind(this); this.childDoSomething = this.childDoSomething.bind(this); } doSomething() {} childDoSomething() {} render() { return ( <div onClick={this.doSomething}> <ChildComponent doSomething={this.childDoSomething} /> </div> ); } } 複製程式碼
建議: 建議採用這種方式,也是react最佳實踐
推薦的寫法。
優缺點:
this react最佳實踐
實現原理:
和渲染時繫結模式實現原理一樣,只是在這種方式下是提前繫結好。
對比:
這種模式和上一種在render
時繫結實現原理是一樣的,這種方式只會繫結一次,效能是好於在render
裡面的繫結;對比下來在寫法上面也有些區別,一個是在constructor
提前繫結,一個是在準備要用的時候懶繫結。
4.屬性賦值模式
/** * 屬性賦值模式 attribute assignment pattern */ class Component extends React.Component { doSomething = () => {} childDoSomething = () => {} render() { return ( <div onClick={this.doSomething}> <ChildComponent doSomething={this.childDoSomething} /> </div> ); } } 複製程式碼
建議: 可以採用,react最佳實踐
也有推薦的這種寫法。
優缺點:
bind
實現原理:
借用箭頭函式在定義的時候就繫結好了this
。
5.高階函式渲染繫結模式
/** * 高階函式渲染繫結模式 higher-order function render bind pattern * 或者叫 高階函式懶繫結模式 higher-order function lazy bind pattern */ class Component extends React.Component { doSomething(data) { return () => { // 使用this, data } } childDoSomething(data) { return () => { // 使用this, data } } render() { return ( <div onClick={this.doSomething()}> <ChildComponent doSomething={this.childDoSomething()} /> </div> ); } } 複製程式碼
建議: 可以採用,嘗試函式式寫法。
優缺點:
- 缺點:不熟悉高階函式(或者函式式),接受起來有難度,需要呼叫一次, 每次都產生新的函式。
- 優點:優雅,高階函式, 可以提前儲存一些變數。
實現原理:
利用高階函式返回箭頭函式, 實現this
的鎖定。
當然這種方法有對應的es5
版本
/** * higher-order function es5 pattern */ class Component extends React.Component { doSomething(data) { cosnt self = this; return function() { // self, data } } childDoSomething(data) { cosnt self = this; return function() { // self, data } } render() { return ( <div onClick={this.doSomething()}> <ChildComponent doSomething={this.childDoSomething()} /> </div> ); } } 複製程式碼
注意:
這種模式被我稱為懶模式,和在render
裡面使用bind
的方式很像,在準備要使用的時候才繫結,每次都產生一個新的函式,可能會帶來效能問題。當然又這種懶模式,我們也有提前繫結模式。
6.高階函式覆寫繫結模式
/** * 高階函式覆寫繫結模式 higher-order function rewrite bind pattern * 或者叫 * 高階函式預繫結模式 higher-order function prepare bind pattern */ class Component extends React.Component { constructor() { this.doSomething = this.doSomething(); this.childDoSomething = this.childDoSomething(); } doSomething(data) { return () => { // 使用this, data } } childDoSomething(data) { return () => { // 使用this, data } } render() { return ( <div onClick={this.doSomething}> <ChildComponent doSomething={this.childDoSomething} /> </div> ); } } 複製程式碼
建議: 可以採用,嘗試函式式寫法。
優缺點:
- 缺點:不熟悉高階函式(或者函式式),接受起來有難度,需要呼叫一次。
- 優點:沒有顯式繫結,在某些場景下可以提前儲存一些變數, 對比上一種模式效能較好。
實現原理:
和上一個高階函式渲染繫結模式
一樣利用高階函式返回箭頭函式, 實現this
的鎖定。不同的是這個模式是在建構函式裡面提前呼叫。繫結後函式只會產生一次。
當然這種方法有對應的es5
版本, 和上個模式的es5
版本很像,也是通過變數快取this
, 不同就是在constructor
裡面呼叫一次函式,而不是在render
裡面。
我是分割線,到了最後一種方式了
7.屬性getter渲染繫結模式
/** * 屬性getter渲染繫結模式 attribute getter render bind pattern * 或者叫 * 屬性getter懶繫結模式 attribute getter lazy bind pattern */ class Component extends React.Component { get doSomething() { // 這裡也可以使用this, 做一些屬性的計算, 比如 this.xxx + this.yyyy return () => { // 使用this } } get childDoSomething() { // 這裡也可以使用this, 做一些屬性的計算, 比如 this.xxx + this.yyyy return () => { // 使用this } } render() { return ( <div onClick={this.doSomething}> <ChildComponent doSomething={this.childDoSomething} /> </div> ); } } 複製程式碼
建議: 可以採用,嘗試新的寫法。
優缺點:
- 缺點:接受起來需要成本, 每次產生新的函式。
- 優點:被標準所支援,沒有顯式繫結,沒有顯式呼叫,比較簡潔優雅,可以提前做一些屬性的聚合或者計算。
實現原理:
借用屬性的getter
, 返回一個箭頭函式繫結this
;
說明:
這種模式和高階函式很像,都是返回一個新的函式,這種模式在特定情況下很強大,簡潔的同時,可以對當前物件的一些屬性做一些計算(是不是很像Vue
的計算屬性), 這種模式下每次getter
後返回的都是一個新的函式,可能會有效能問題,但是如果對其他屬性進行了聚合計算,或者說是依賴其他屬性的最新值
,就需要在render
裡面getter
,以保證依賴的屬性都是乾淨的值
(最新的值);
當然大家知道里面返回的是箭頭函式,肯定也有es5
版本,其實和其他模式的es5
版本都很像,在這裡就不寫了。既然這種模式下有可能產生效能問題,對比其他模式,我們可定也有預繫結模式
。請往下看
8.屬性getter賦值繫結模式
/** * 屬性getter賦值繫結模式attribute getter assignment bind pattern * 或者叫 * 屬性getter預繫結模式 attribute getter prepare bind pattern */ class Component extends React.Component { constructor() { this.doSomethingBind = this.doSomething; this.childDoSomethingBind = this.childDoSomething } get doSomething() { // 這裡也可以使用this, 做一些屬性的計算, 比如 this.xxx + this.yyyy return () => { // 使用this } } get childDoSomething() { // 這裡也可以使用this, 做一些屬性的計算, 比如 this.xxx + this.yyyy return () => { // 使用this } } render() { return ( <div onClick={this.doSomethingBind}> <ChildComponent doSomething={this.childDoSomethingBind} /> </div> ); } } 複製程式碼
建議: 可以採用,嘗試新的寫法。
優缺點:
- 缺點:接受起來需要成本,賦值的函式需要另外一個名字 。
- 優點:被標準所支援,沒有顯式繫結,只產生一次函式,比較簡潔優雅,可以提前做一些屬性的聚合或者計算。
實現原理:
借用屬性的getter
, 返回一個箭頭函式繫結this
;賦值給物件的另外一個屬性,呼叫的是另外一個方法。
說明:
這種模式和上一種模式區別在於,提前繫結,只會產生一次函式。但是要注意不是重寫函式,而是賦值給另外一個不同的方法名,可能大家覺得這種換名字不夠好,但是換個角度考慮一下,系物件多了一個方法,同時又持有之前的getter
,這樣可以更加的靈活,可以選擇性的使用這兩種函式。
同其他一些返回箭頭的模式一樣,這種模式依然有es5
版本,寫法同上,不在贅述。
總結
上面列舉的這些模式,不一定是全部寫法,不過足以應對工作中的大多數場景,同時有些模式還可以讓我們去接觸另外的實現方式。列舉了這麼多種,每一種都有優劣,工作中可以選擇性的去使用,看場景和團隊風格。
展望
既然JavaScript
中this
的問題一直困擾著我們,那麼有沒有一種方式可以不使用this
,就可以實現我們想要的所有功能,答案是肯定的。React 16.7.0-alpha
版本加入了特別神奇的hooks
(好像Vue 3.0裡面也已經加入了相似的特性),可以讓我們徹底擺脫this
的困擾(當然this
依然是js
裡面一個神奇的存在),同時讓我們的程式碼更加函式式
,更大程度的複用處理邏輯
,當然這個特性還在等待成為事實標準,我們希望這一天很快到來,不過我們仍然可以現在就是使用它。