為什麼我們要新增 super(props) ?

boomyao發表於2019-02-10

我聽說 Hooks 成了新的焦點。但是呢,我想通過這篇部落格來介紹下class宣告元件有趣的地方。意下如何?

下面內容無法提高你的React使用技巧。但是,當你深入探究事物是如何執行時,將會發現它們所帶來的喜悅之情。

首先來看看第一個例子。


我寫super(props)的次數比我想象中多得多:

class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}
複製程式碼

當然,class fields proposal(JS提案)使我們跳過這個過程:

class Checkbox extends React.Component {
  state = { isOn: true };
  // ...
}
複製程式碼

這是在2015年,在React0.13版本時新增支援的類語法planned。在class fields這個更合理的替代方案出來之前,宣告constructor和呼叫super(props)一直被做為一種臨時的解決方案。

但在此之前,讓我們回到只使用ES2015的例子:

class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}
複製程式碼

為什麼要呼叫super?我們可以呼叫它嗎?如果我們必須呼叫它,那呼叫時不傳props會怎麼樣呢?會有更多的引數嗎?來一起找找答案。


JavaScript中,super是父類constructor的引用。(我們例子中,它指向React.Component

很重要的一點,你是無法在父類的constructor呼叫之前在constructor中使用this的,JavaScript不允許你這樣做:

class Checkbox extends React.Component {
  constructor(props) {
    // ? Can’t use `this` yet
    super(props);
    // ✅ Now it’s okay though
    this.state = { isOn: true };
  }
  // ...
}
複製程式碼

JavaScript 會強制父類constructor在你碰 this 前被執行是有原因的。想想類的層次結構:

class Person {
  constructor(name) {
    this.name = name;
  }
}

class PolitePerson extends Person {
  constructor(name) {
    this.greetColleagues(); // ? This is disallowed, read below why
    super(name);
  }
  greetColleagues() {
    alert('Good morning folks!');
  }
}
複製程式碼

假設在super之前允許呼叫this。一個月之後,我們可能在greetColleagues的訊息中加入了name屬性:

  greetColleagues() {
    alert('Good morning folks!');
    alert('My name is ' + this.name + ', nice to meet you!');
  }
複製程式碼

然而我們忘記了宣告this.namesuper方法被呼叫之前,已經呼叫了this.greetColleagues()。以至於this.name變成了undefined的狀態!如你所見,程式碼會因此變得難以揣測。

為了避免這種陷阱,JavaScript 強制要求, 如果想在constructor裡使用this,就必須先呼叫super。讓父類做好它的事先!這個限制也適用於定義別的React元件:

  constructor(props) {
    super(props);
    // ✅ Okay to use `this` now
    this.state = { isOn: true };
  }
複製程式碼

還有另外一個問題,為什麼要傳props


你可能會認為,之所以super裡要傳props,是為了在React.Component的constructor裡初始化this.props

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}
複製程式碼

實際上也差不多是這個原因,這是確切原因

但在一些時候,即使在呼叫super時不傳props引數,你仍然可以在render和其他方法中獲取到this.props。(如果你不信,自己試下咯!)

這是如何實現的呢?原因是,在你的元件例項化後,會賦值props屬性給例項物件。

  // Inside React
  const instance = new YourComponent(props);
  instance.props = props;
複製程式碼

所以即使忘記傳propssuper,React仍然會在之後設定它們,這是有原因的。

當React新增對類的支援時,它不僅僅增加了對ES6的支援,目標是儘可能支援廣泛的類抽象化。當時我們還不清楚如ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript或其他解決方案怎樣算成功地定義元件,所以React也就不關心是否需要呼叫super()了——即便是ES6。

所以說是可以只用super()來替代super(props)嗎?

最好不要。因為這樣仍然有問題。沒錯,React可以在你的constructor執行後給this.props賦值。但this.props在呼叫super和constructor結束前仍然是undefined

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

// Inside your code
class Button extends React.Component {
  constructor(props) {
    super(); // ? We forgot to pass props
    console.log(props);      // ✅ {}
    console.log(this.props); // ? undefined 
  }
  // ...
}
複製程式碼

如果在constructor中有某些方法存在這種情況,它將會變得難以除錯。這也是為什麼我一直建議新增super(props),即使沒有需要:

class Button extends React.Component {
  constructor(props) {
    super(props); // ✅ We passed props
    console.log(props);      // ✅ {}
    console.log(this.props); // ✅ {}
  }
  // ...
}
複製程式碼

這確保了this.props在constructor完成之前就被賦值。


最後還有一點是長期以來React使用者可能會感到好奇的。

你可能會注意到當你在類中使用 Context API(無論是過去的contextTypes或是後來在React 16.6中新增的contextTypeAPI,context都會做為constructor的第二個引數用來傳遞)。

所以我們為什麼不用super(props, context)來替代呢?其實我們可以,但 context 的使用頻率較低,所以遇到的坑沒有那麼多。

當有了class fields proposal,大部分的坑都會消失。在沒有標明constructor的情況下,全部引數會被自動傳入。這樣就允許像state = {}的表示式,如果有需要this.props或者this.context將同樣適用。

Hooks中,我們甚至不需要super或者this。但這要改天再說。

翻譯原文Why Do We Write super(props)?

相關文章