原文:overreacted.io/why-do-we-w…javascript
我據說 Hooks 最近很火。諷刺的是,我想用一些關於 class 組件的有趣故事來開始這篇文章。你以爲如何?html
本文中這些坑對於你正常使用 React 並非很重要。 可是假如你想更深刻的瞭解它的運做方式,就會發現實際上它們頗有趣。java
開始第一個。react
首先在個人職業生涯中寫過的 super(props)
本身都記不清:git
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
複製代碼
固然,在類字段提案 (class fields proposal) 中建議讓咱們跳過這個開頭:github
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
複製代碼
在2015年 React 0.13 增長對普通類的支持時,曾經打算用這樣的語法。定義 constructor
和調用 super(props)
始終是一個臨時的解決方案,可能要等到類字段可以提供在工程學上不那麼反人類的替代方案。typescript
不過仍是讓咱們回到前面這個例子,此次只用ES2015的特性:ide
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
複製代碼
爲何咱們要調用super
? 能夠調用它嗎? 若是必需要調用,不傳遞prop
參數會發生什麼? 還有其餘參數嗎? 接下來咱們試一試:函數
在 JavaScript 中,super
指的是父類的構造函數。(在咱們的示例中,它指向React.Component
的實現。)ui
重要的是,在調用父類構造函數以前,你不能在構造函數中使用this
。 JavaScript 是不會讓你這樣作的:
class Checkbox extends React.Component {
constructor(props) {
// 🔴 這裏還不能用 `this`
super(props);
// ✅ 如今能夠用了
this.state = { isOn: true };
}
// ...
}
複製代碼
爲何 JavaScript 在使用this
以前要先強制執行父構造函數,有一個很好的理由可以解釋。 先看下面這個類的結構:
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!');
}
複製代碼
可是咱們忘記了 super()
在設置 this.name
以前先調用了 this.greetColleagues()
。 因此此時 this.name
尚未定義! 如你所見,像這樣的代碼很難想到問題出在哪裏。
爲了不這類陷阱,JavaScript 強制要求:若是想在構造函數中使用this
,你必須首先調用super
。 先讓父類作完本身的事! 這種限制一樣也適用於被定義爲類的 React 組件:
constructor(props) {
super(props);
// ✅ 在這裏能夠用 `this`
this.state = { isOn: true };
}
複製代碼
這裏又給咱們留下了另外一個問題:爲何要傳 props
參數?
你可能認爲將props
傳給super
是必要的,這可使 React.Component
的構造函數能夠初始化this.props
:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
複製代碼
這與正確答案很接近了 —— 實際上它就是這麼作的。
可是不知道爲何,即使是你調用 super
時沒有傳遞 props
參數,仍然能夠在 render
和其餘方法中訪問this.props
。 (不信你能夠親自去試試!)
這是到底是爲何呢? 事實證實,在調用構造函數後,React也會在實例上分配 props
:
// Inside React
const instance = new YourComponent(props);
instance.props = props;
複製代碼
所以,即便你忘記將props
傳給 super()
,React 仍然會在以後設置它們。 這是有緣由的。
當 React 添加對類的支持時,它不只僅增長了對 ES6 類的支持。它的目標是儘量普遍的支持類抽象。 目前還不清楚 ClojureScript、CoffeeScript、ES六、Fable、Scala.js、TypeScript或其餘解決方案是如何相對成功地定義組件的。 因此 React 故意不關心是否須要調用 super()
—— 即便是ES6類。
那麼這是否是就意味着你能夠寫 super()
而不是super(props)
呢?
可能不行,由於它仍然是使人困惑的。 固然,React 稍後會在你的構造函數運行後分配 this.props
, 可是在調用 super()
以後和構造函數結束前這段區間內 this.props
仍然是未定義的:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// Inside your code
class Button extends React.Component {
constructor(props) {
super(); // 😬 咱們忘記了傳遞 props 參數
console.log(props); // ✅ {}
console.log(this.props); // 😬 undefined
}
// ...
}
複製代碼
若是這種狀況發生在從構造函數調用的某個方法中,可能會給調試工做帶來很大的麻煩。 這就是爲何我建議老是調用 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中新添加的 contextType
API),context
會做爲第二個參數傳遞給構造函數。
那麼爲何咱們不寫成 super(props, context)
呢? 咱們能夠這樣作,可是使用 context
的頻率較低,因此這個坑並無那麼多影響。
根據類字段提案的說明,這些坑大部分都會消失。 若是沒有顯式構造函數,則會自動傳遞全部參數。 這容許在像 state = {}
這樣的表達式中包含對 this.props
或 this.context
的引用(若是有必要的話)。
而有了 Hooks 以後,咱們甚至再也不有 super
或 this
。 不過這是另一個的話題了。