為什么要寫super(props)

用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.propsthis.context 的內(nèi)容:

class Button extends React.Component {
  state = {
    age: this.props.age,
    name: this.context.name
  }

  // ...
}

當(dāng)然,有了 Hooks 以后,幾乎就不需要 super 和 this 了,但那就是另一個(gè)概念了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容