從 React 繫結 this,看 JS 語言發展和框架設計

LucasHC發表於2019-03-04

在 javascript 語言中,關於 this 這個關鍵字的行為一直以來困擾著一代又一代初級開發者。同時 this,也充分反應了 javascript 的詭異與靈活。

但是請別誤會,這篇文章並不會對 this 的特徵進行全方位講解,因為這些內容都可以在各種前端書籍中找到答案。這裡,我試圖結合 React 事件處理函式關於 this 繫結的演化史,談一談這個框架設計以及 javascript 語言在這一細節上的進步和完善。同時對比 this 繫結的不同方案,讓大家對 React 、ES next 有一個更清晰的認識。

React 處理 this 上下文環境已經有至少五年曆史了。五年期間,方案輩出,我們先來總結一下。

方法一:React.createClass 自動繫結

React 中建立元件的方式已經很多,比較古老的諸如 React.createClass 應該很多人並不陌生。當然,從 React 0.13 開始,可以使用 ES6 Class 代替 React.createClass 了,這應該是今後推薦的方法。
但是需要知道,React.createClass 建立的元件,可以自動繫結 this。也就是說,this 這個關鍵字會自動繫結在元件例項上面。

// This magically works with React.createClass
// because `this` is bound for you.
onChange = {this.handleChange}複製程式碼

當然很遺憾,對於元件的建立,官方已經推薦使用 class 宣告元件或使用 functional 無狀態元件:

Later, classes were added to the language as part of ES2015, so we added the ability to create React components using JavaScript classes. Along with functional components, JavaScript classes are now the preferred way to create components in React.
For your existing createClass components, we recommend that you migrate them to JavaScript classes.

我認為,這其實是 React 框架本身的自我完善和對未來的迎合,是框架和語言發展的大勢所趨。

方法二:渲染時繫結

通過前文,我們知道最傳統的元件建立方式不會有 this 繫結的困擾。接下來,我們假定所有的元件都採取 ES6 classes 方式宣告。這種情況下,this 無法自動繫結。一個常見的解決方案便是:

onChange = {this.handleChange.bind(this)}複製程式碼

這種方法簡明扼要,但是有一個潛在的效能問題:當元件每次重新渲染時,都會有一個新的函式建立。OMG! 這聽上去貌似是一個很大的問題,但是其實在真正的開發場景中,由此引發的效能問題往往不值一提(除非是大型元件消費類應用或遊戲)。

方法三:箭頭函式繫結

這種方法其實和第二種類似,拜 ES6 箭頭函式所賜,我們可以隱式繫結 this:

onChange = {e => this.handleChange(e)}複製程式碼

當然,也與第二種方法一樣,它同樣存在潛在的效能問題
下面將要介紹的兩種方法,可以有效規避不必要的效能消耗,請繼續閱讀。

方法四:Constructor 內繫結

constructor 方法是類的預設方法,通過new命令生成物件例項時,自動呼叫該方法。

所以我們可以:

constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
}複製程式碼

這種方式往往被推薦為“最佳實踐”,也是筆者最為常用的方法。

但是就個人習慣而言,我認為與前兩種方法相比,constructor 內繫結在可讀性和可維護性上也許有些欠缺。
同時,我們知道在 constructor 宣告的方法不會存在例項的原型上,而屬於例項本身的方法。每個例項都有同樣一個 handleChange,這本身也是一種重複和浪費。

如果你對 ES next 一直抱有開放的思想,且能夠使用 stage-2 的特性,不妨嘗試一下最後一種方案。

方法五:Class 屬性中使用 = 和箭頭函式

這個方法依賴於 ES next 的新特性,請參考 tc 39: Public Class Fields

handleChange = () => {
      // call this function from render 
      // and this.whatever in here works fine.
};複製程式碼

我們來總結一下這種方式的優點:

  • 使用箭頭函式,有效繫結了 this;
  • 沒有第二種方法和第三種方法的潛在效能問題;
  • 避免了方法四的元件例項重複問題;
  • 我們可以直接從 ES5 createClass 重構得來。

總結

本文在對比 React 繫結 this 的五種方法的同時,也由遠及近了解了 javascript 語言的發展:從 ES5 的 bind, 到 ES6 的箭頭函式,再到 ES next 對 class 的改進。

React 作為蓬勃發展的框架也同樣在與時具進,不斷完善,結合語言特性的發展不斷調整著自身。

最後,我們通過這張圖片來完整回顧:

各種方式邏輯
各種方式邏輯

本文參考了 Cory House 的文章:5 Approaches for Handling this,並在此基礎上進行延伸。

我的其他一些關於 React 文章:

Happy Coding!

PS: 作者Github倉庫,歡迎通過程式碼各種形式交流。

相關文章