用React開發(fā)class組件時(shí),constructor中一定要調(diào)用 super(props)。
下面通過兩個(gè)問題逐步分析。第一:為什么要調(diào)用super?第二:為什么要傳入props,不傳會(huì)發(fā)生什么?
首先解釋第一個(gè)問題:
在 JavaScript 子類的構(gòu)造函數(shù)中 super 指的是父類(即超類)的構(gòu)造函數(shù)。子類中顯式定義了constructor的方法中必須在其最頂層調(diào)用super,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇愖约旱膖his對(duì)象,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后再對(duì)其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類就得不到this對(duì)象。所以必須先調(diào)用super才可以使用this。如果子類沒有定義constructor方法,這個(gè)方法會(huì)被默認(rèn)添加。
如下:
class A {}
class B extends A {
constructor() {
super()
}
}
new B().constructor === B // true
以上結(jié)論證明super雖然代表了父類A的構(gòu)造函數(shù),但是返回的是子類B的實(shí)例,即super內(nèi)部的this指的是B的實(shí)例,因此super()相當(dāng)于A.prototype.constructor.call(this)。
再通過 new.target 驗(yàn)證:
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super()
}
}
new B() // B
new.target指向new命令作用的構(gòu)造函數(shù),以上 new.target.name 為 B,由此可知在super()執(zhí)行時(shí),它指向的是子類B的構(gòu)造函數(shù),而不是父類A的構(gòu)造函數(shù)。也就是說,super()內(nèi)部的this指向的是B。
在React中,super指向了 React.Component,所以在調(diào)用父類的構(gòu)造函數(shù)之前,是不能在 constructor 中使用 this 關(guān)鍵字的。
class Button extends React.Component {
constructor() {
// 還不能訪問 `this`
super();
// 可以訪問
this.state = { show: true }
}
// ...
}
第二個(gè)問題,為什么要傳props?
為了讓 React.Component 構(gòu)造函數(shù)初始化 this.props。React源碼是這樣的:
function Component(props, context) {
this.props = props;
this.context = context;
// ...
}
但是有些時(shí)候在調(diào)用 super() 的時(shí)即使沒有傳入 props,依然能夠在 render 函數(shù)或其他方法中訪問到 this.props。那這是怎么做到的呢?事實(shí)證明,React 在調(diào)用構(gòu)造函數(shù)后也立即將 props 賦值到了實(shí)例上:
// React 內(nèi)部
const instance = new YourComponent(props);
instance.props = props;
所以即便忘記了將 props 傳給 super(),React 也仍然會(huì)在之后將它定義到實(shí)例上。這樣做是有原因的:
當(dāng) React 增加了對(duì)類的支持時(shí),不僅增加了對(duì)ES6類的支持。其目標(biāo)是盡可能廣泛的支持類抽象。當(dāng)時(shí)尚不清楚 ClojureScript,CoffeeScript,ES6,F(xiàn)able,Scala.js,TypeScript 或其他解決方案在類組件方面是否成功。因此 React 刻意地沒有顯式要求調(diào)用 super()。
那是不是意味著能夠用 super() 代替 super(props) 嗎?
最好不要這樣做,這樣寫在邏輯上并不能確定沒問題,因?yàn)镽eact 會(huì)在構(gòu)造函數(shù)執(zhí)行完畢之后才給 this.props 賦值。但這樣做會(huì)使得 this.props 在 super 調(diào)用一直到構(gòu)造函數(shù)結(jié)束期間值為 undefined。
class Button extends React.Component {
constructor(props) {
super(); // 忘了傳入 props
console.log(props); // {}
console.log(this.props); // undefined
}
// ...
}
如果在構(gòu)造函數(shù)中調(diào)用了內(nèi)部的其他方法,那么一旦出錯(cuò)這會(huì)使得調(diào)試過程阻力變大。這就是為什么建議開發(fā)者一定執(zhí)行 super(props) 的原因。
class Button extends React.Component {
constructor(props) {
super(props) // 傳入 props
console.log(props) // {}
console.log(this.props) // {}
}
// ...
}
這樣就確保了 this.props 在構(gòu)造函數(shù)執(zhí)行完畢之前已被賦值。
此外,還有一點(diǎn)是 React 開發(fā)者長(zhǎng)期以來的好奇之處。
當(dāng)在組件中使用Context API 的時(shí)候,context會(huì)作為第二個(gè)參數(shù)傳入constructor,那么為什么我們不寫成 super(props, context) 呢?可以,但 context 的使用頻率較低,因而沒有必要。
而且 class fields proposal 出來后,在沒有顯示定義構(gòu)造函數(shù)的情況下,以上屬性都會(huì)被自動(dòng)地初始化。使得像 state = {} 這類表達(dá)式能夠在需要的情況下引用 this.props 和 this.context 的內(nèi)容:
class Button extends React.Component {
state = {
age: this.props.age,
name: this.context.name
}
// ...
}
當(dāng)然,有了 Hooks 以后,幾乎就不需要 super 和 this 了,但那就是另一個(gè)概念了。