為什麼我們要寫 super(props) ?
2018 M11 30 • ☕️ 3 min read
Translated by readers into: Deutsch • Español • Français • Italiano • Magyar • Nederlands • Norsk • Português do Brasil • Slovenčina • Tiếng Việt • Türkçe • srpski • Čeština • Українська • فارسی • ไทย • မြန်မာဘာသာ • 日本語 • 简体中文 • 繁體中文
Read the original • Improve this translation • View all translated posts
我聽說 Hooks 正夯, 但我想要從談論 class 有趣的小知識來開始這個部落格。如何?
這些解釋對於有效地使用 React 並不重要,但對於喜歡深入了解其運作的你,將會發現當中有趣的事情。
首先,我生命中寫過 super(props)
的次數比我想要知道的還多:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
沒錯,類別欄位提案 讓我們能省略宣告:
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
會發生什麽事?還有其他有參數嗎? 我們來看看。
在 JavaScript 中,super
會參照父類別的建構子。(在我們的例子當中,它會指向 React.Component
的實作。)
重要的是,直到你呼叫父類別的建構子後,你才能在建構子中使用 this
。JavaScript 不會讓你這麼做:
class Checkbox extends React.Component {
constructor(props) {
// 🔴 還不能用 `this`
super(props);
// ✅ 現在沒問題了
this.state = { isOn: true };
}
// ...
}
JavaScript 會強制父建構子在你碰 this
前被執行是有原因的。想想類別的階層:
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
的訊息中:
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
但是我們忘了 this.greetColleagues()
是在 super()
有機會設置 this.name
之前就被呼叫了。所以 this.name
根本不曾被定義過!如你所見,這樣的程式碼是很難理解的。
為了避免踩這種雷,如果你想在建構子中使用 this
,JavaScript 強制你必須先呼叫 super
。讓父類別去做它的事!這個限制也適用於定義為類別的 React 元件:
constructor(props) {
super(props);
// ✅ 能使用 `this` 了
this.state = { isOn: true };
}
這裡留下了另一個問題:為什麼要帶入 props
?
你可能會想,帶入 props
到 super
是必須的,因為底層的 React.Component
建構子才能初始化 this.props
:
// React 內部
class Component {
constructor(props) {
this.props = props;
// ...
}
}
這離真實情況相去不遠 — 的確,它是 這麼做 的。
但不知為何,即使你呼叫的是沒有帶入 props
參數的 super()
,你仍然可以在 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); // 😬 未定義(undefined) }
// ...
}
如果某些從建構子中被呼叫的函式發生以上這種狀況,除錯將會更加艱難。這就是為什麽我建議開發者總是要帶入 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 新增的新式 contextType
API),context
會作為第二個參數傳給建構子。
所以為什麼我們不取而代之寫成 super(props, context)
呢?其實我們可以,只是 context 使用的頻率比較低,會踩到的雷並不是那麼多。
當有了類別欄位提案,整個這種的雷大部份都會消失。在沒有標明建構子的狀況下,所有的參數都會自動地被帶入。這樣就允許了像 state = {}
這樣的表達式,在有需要的狀況下,還是能包含參考(reference) this.props
跟 this.context
的能力。
而當有了 Hooks 後,我們甚至不需要 super
或是 this
。但這就改天再說了。