[第20期] 爲何咱們要寫 super(props) ?

轉載自 https://overreacted.io/zh-han...

聽說 Hooks 勢頭正盛,不過我仍是想略帶調侃地從 class 的有趣之處開始這篇博客。可還行?javascript

這些梗對於使用 React 輸出產品並不重要,但若是你想深刻的瞭解它們的運做原理,它們會很是的有用。前端

首先,在這一輩子中,super(props) 出如今我代碼裏的次數比我知道的還要多:java

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

固然了,咱們能夠經過 class fields proposal 來省略這個聲明:react

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,又會發生什麼呢?會有其餘的缺省參數嗎?接來下咱們就解開這一系列謎題。this

在 JavaScript 中,super 指的是父類(即超類)的構造函數。(在咱們的例子中,它指向了 React.Component 的實現。)spa

值得注意的是,在調用父類的構造函數以前,你是不能在 constructor 中使用 this 關鍵字的。JavaScript 不容許這個行爲。調試

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴  還不能使用 `this`
    super(props);
    // ✅  如今能夠了
    this.state = { isOn: true };
  }
  // ...
}

JavaScript 有足夠合理的動機來強制你在接觸 this 以前執行父類構造函數。考慮考慮一些類層次結構的東西:code

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 打印的消息中使用了 person 的 name 屬性:

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

可是咱們並未想起 this.greetColleagues 在 super() 給 this.name 賦值前就已經執行。this.name 此時甚至還沒有定義。能夠看到,這樣的代碼難以往下推敲。

爲了不落入這個陷阱,JavaScript 強制你在使用 this 以前先行調用 super。讓父類來完成這件事情!:

constructor(props) {
  super(props);
  // ✅ 能使用 `this` 了
  this.state = { isOn: true };
}

這裏留下了另外一個問題:爲何要傳入 props ?

你或許會想到,爲了讓 React.Component 構造函數可以初始化 this.props,將 props 傳入 super 是必須的:

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

這幾乎就是真相了 — 確然,它是 這樣作 的。

但有些撲朔迷離的是,即使你調用 super() 的時候沒有傳入 props,你依然可以在 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); // 😬 未定義
  }
  // ...
}

若是在構造函數中調用了其餘的內部方法,那麼一旦出錯這會使得調試過程阻力更大。這就是我建議開發者必定執行 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 更新的新版 contextTypes)的時候,context 是做爲第二個參數傳入構造函數的。

那麼爲何咱們不能轉而寫成 super(props, context) 呢?咱們固然能夠,但 context 的使用頻率較低,於是並無掘這個坑。

class fields proposal 出臺後,這些坑大部分都會天然地消失在沒有顯示的定義構造函數的狀況下,以上的屬性都會被自動地初始化。這使得像 state = {} 這類表達式可以在須要的狀況下引用 this.props 和 this.context 的內容。

然而,有了 Hooks 之後,咱們幾乎就不須要 super 和 this 了。但那就是另外一個下午的茶點了。


前端收藏家(微信號: fedaily)
收集全網優秀前端技術資訊,與你分享,共同成長。

前端收藏家.jpg

相關文章
相關標籤/搜索