轉載自 https://overreacted.io/zh-han...
聽說 Hooks 勢頭正盛,不過我仍是想略帶調侃地從 class 的有趣之處開始這篇博客。可還行?javascript
這些梗對於使用 React 輸出產品並不重要,但若是你想深刻的瞭解它們的運做原理,它們會很是的有用。前端
首先,在這一輩子中,super(props)
出如今我代碼裏的次數比我知道的還要多:java
class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ... }
固然了,咱們能夠經過 class fields proposal
來省略這個聲明:react
class Checkbox extends React.Component { state = { isOn: true }; // ... }
早在 2015 年 React 0.13 已經計劃支持 。在當時,聲明 constructor
和調用 super(props)
一直被視做暫時的解決方案,直到有合適的類字段聲明形式。微信
但在此以前,咱們先回到 ES2015 風格的代碼:函數
class Checkbox extends React.Component { constructor(props) { super(props); this.state = { isOn: true }; } // ... }
爲何咱們要調用 super,咱們能夠不這麼作嗎?那麼在咱們調用它時不傳入 props,又會發生什麼呢?會有其餘的缺省參數嗎?接來下咱們就解開這一系列謎題。this
在 JavaScript 中,super 指的是父類(即超類)的構造函數。(在咱們的例子中,它指向了 React.Component 的實現。)spa
值得注意的是,在調用父類的構造函數以前,你是不能在 constructor 中使用 this 關鍵字的。JavaScript 不容許這個行爲。調試
class Checkbox extends React.Component { constructor(props) { // 🔴 還不能使用 `this` super(props); // ✅ 如今能夠了 this.state = { isOn: true }; } // ... }
JavaScript 有足夠合理的動機來強制你在接觸 this 以前執行父類構造函數。考慮考慮一些類層次結構的東西:code
class Person { constructor(name) { this.name = name; } } class PolitePerson extends Person { constructor(name) { this.greetColleagues(); // 🔴 這是禁止的,日後見緣由 super(name); } greetColleagues() { alert('Good morning folks!'); } }
試想一下,在調用 super 以前使用 this 不被禁止的狀況下,一個月後,咱們可能在 greetColleagues
打印的消息中使用了 person 的 name 屬性:
greetColleagues() { alert('Good morning folks!'); alert('My name is ' + this.name + ', nice to meet you!'); }
可是咱們並未想起 this.greetColleagues
在 super() 給 this.name 賦值前就已經執行。this.name 此時甚至還沒有定義。能夠看到,這樣的代碼難以往下推敲。
爲了不落入這個陷阱,JavaScript 強制你在使用 this 以前先行調用 super。讓父類來完成這件事情!:
constructor(props) { super(props); // ✅ 能使用 `this` 了 this.state = { isOn: true }; }
這裏留下了另外一個問題:爲何要傳入 props ?
你或許會想到,爲了讓 React.Component 構造函數可以初始化 this.props,將 props 傳入 super 是必須的:
// React 內部 class Component { constructor(props) { this.props = props; // ... } }
這幾乎就是真相了 — 確然,它是 這樣作 的。
但有些撲朔迷離的是,即使你調用 super() 的時候沒有傳入 props,你依然可以在 render 函數或其餘方法中訪問到 this.props。(若是你質疑這個機制,嘗試一下便可)
那麼這是怎麼作到的呢?事實證實,React 在調用構造函數後也當即將 props 賦值到了實例上:**
// React 內部 const instance = new YourComponent(props); instance.props = props;
所以即使你忘記了將 props 傳給 super(),React 也仍然會在以後將它定義到實例上。這麼作是有緣由的。
當 React 增長了對類的支持時,不只僅是爲了服務於 ES6。其目標是儘量普遍地支持類抽象。當時咱們 不清楚 ClojureScript,CoffeeScript,ES6,Fable,Scala.js,TypeScript
等解決方案是如何成功的實踐組件定義的。於是 React 刻意地沒有顯式要求調用 super() —— 即使 ES6 自身就包含這個機制。
這意味着你可以用 super() 代替 super(props) 嗎?
最好不要,畢竟這樣寫在邏輯上並不明確確然,React 會在構造函數執行完畢以後給 this.props 賦值。但如此爲之會使得 this.props 在 super 調用一直到構造函數結束期間值爲 undefined。
// React 內部 class Component { constructor(props) { this.props = props; // ... } } // 你的程式碼內部 class Button extends React.Component { constructor(props) { super(); // 😬 咱們忘了傳入 props console.log(props); // ✅ {} console.log(this.props); // 😬 未定義 } // ... }
若是在構造函數中調用了其餘的內部方法,那麼一旦出錯這會使得調試過程阻力更大。這就是我建議開發者必定執行 super(props) 的緣由,即便理論上這並不是必要:
class Button extends React.Component { constructor(props) { super(props); // ✅ 傳入 props console.log(props); // ✅ {} console.log(this.props); // ✅ {} } // ... }
確保了 this.props 在構造函數執行完畢以前已被賦值。
最後,還有一點是 React 愛好者長期以來的好奇之處。
你會發現當你在類中使用 Context API (不管是舊版的 contextTypes 或是在 React 16.6 更新的新版 contextTypes)的時候,context 是做爲第二個參數傳入構造函數的。
那麼爲何咱們不能轉而寫成 super(props, context)
呢?咱們固然能夠,但 context 的使用頻率較低,於是並無掘這個坑。
class fields proposal 出臺後,這些坑大部分都會天然地消失在沒有顯示的定義構造函數的狀況下,以上的屬性都會被自動地初始化。這使得像 state = {} 這類表達式可以在須要的狀況下引用 this.props 和 this.context 的內容。
然而,有了 Hooks 之後,咱們幾乎就不須要 super 和 this 了。但那就是另外一個下午的茶點了。