用React開(kāi)發(fā)class組件時(shí)轧苫,constructor中一定要調(diào)用 super(props)
樟遣。
下面通過(guò)兩個(gè)問(wèn)題逐步分析茧妒。第一:為什么要調(diào)用super?第二:為什么要傳入props祠丝,不傳會(huì)發(fā)生什么疾呻?
首先解釋第一個(gè)問(wèn)題:
在 JavaScript 子類(lèi)的構(gòu)造函數(shù)中 super 指的是父類(lèi)(即超類(lèi))的構(gòu)造函數(shù)。子類(lèi)中顯式定義了constructor的方法中必須在其最頂層調(diào)用super写半,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇?lèi)自己的this對(duì)象尉咕,必須先通過(guò)父類(lèi)的構(gòu)造函數(shù)完成塑造叠蝇,得到與父類(lèi)同樣的實(shí)例屬性和方法,然后再對(duì)其進(jìn)行加工年缎,加上子類(lèi)自己的實(shí)例屬性和方法悔捶。如果不調(diào)用super方法,子類(lèi)就得不到this對(duì)象单芜。所以必須先調(diào)用super才可以使用this蜕该。如果子類(lèi)沒(méi)有定義constructor方法,這個(gè)方法會(huì)被默認(rèn)添加洲鸠。
如下:
class A {}
class B extends A {
constructor() {
super()
}
}
new B().constructor === B // true
以上結(jié)論證明super雖然代表了父類(lèi)A的構(gòu)造函數(shù)堂淡,但是返回的是子類(lèi)B的實(shí)例,即super內(nèi)部的this指的是B的實(shí)例扒腕,因此super()相當(dāng)于A.prototype.constructor.call(this)
绢淀。
再通過(guò) 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í)皆的,它指向的是子類(lèi)B的構(gòu)造函數(shù),而不是父類(lèi)A的構(gòu)造函數(shù)蹋盆。也就是說(shuō)费薄,super()內(nèi)部的this指向的是B。
在React中栖雾,super指向了 React.Component楞抡,所以在調(diào)用父類(lèi)的構(gòu)造函數(shù)之前,是不能在 constructor 中使用 this 關(guān)鍵字的岩灭。
class Button extends React.Component {
constructor() {
// 還不能訪(fǎng)問(wèn) `this`
super();
// 可以訪(fǎng)問(wèn)
this.state = { show: true }
}
// ...
}
第二個(gè)問(wèn)題拌倍,為什么要傳props?
為了讓 React.Component 構(gòu)造函數(shù)初始化 this.props。React源碼是這樣的:
function Component(props, context) {
this.props = props;
this.context = context;
// ...
}
但是有些時(shí)候在調(diào)用 super() 的時(shí)即使沒(méi)有傳入 props柱恤,依然能夠在 render 函數(shù)或其他方法中訪(fǎng)問(wèn)到 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ì)類(lèi)的支持時(shí)寺谤,不僅增加了對(duì)ES6類(lèi)的支持仑鸥。其目標(biāo)是盡可能廣泛的支持類(lèi)抽象。當(dāng)時(shí)尚不清楚 ClojureScript变屁,CoffeeScript眼俊,ES6,F(xiàn)able粟关,Scala.js疮胖,TypeScript 或其他解決方案在類(lèi)組件方面是否成功。因此 React 刻意地沒(méi)有顯式要求調(diào)用 super()闷板。
那是不是意味著能夠用 super() 代替 super(props) 嗎澎灸?
最好不要這樣做,這樣寫(xiě)在邏輯上并不能確定沒(méi)問(wèn)題遮晚,因?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)試過(guò)程阻力變大糜颠。這就是為什么建議開(kāi)發(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 開(kāi)發(fā)者長(zhǎng)期以來(lái)的好奇之處。
當(dāng)在組件中使用Context API 的時(shí)候饭聚,context會(huì)作為第二個(gè)參數(shù)傳入constructor忌警,那么為什么我們不寫(xiě)成 super(props, context) 呢?可以秒梳,但 context 的使用頻率較低法绵,因而沒(méi)有必要。
而且 class fields proposal
出來(lái)后酪碘,在沒(méi)有顯示定義構(gòu)造函數(shù)的情況下朋譬,以上屬性都會(huì)被自動(dòng)地初始化。使得像 state = {}
這類(lèi)表達(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è)概念了。