我聽說現在Hooks是新的熱點。諷刺地是,我想描述類的相關事實作為這片部落格的開始。那是怎麼樣的呢?
這些坑對於有效地使用React並不重要。但如果你想更深入地瞭解事物的工作原理,你可能會發現它們很有趣。
這是第一個。
我的生命中我寫的 super(props)
在我的生命中我寫的super(props)
比我知道的多:
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
}
// ...
複製程式碼
當然,類相關的提議讓我們跳過了這個儀式:
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
複製程式碼
2015年React 0.13增加了對純類的支援時,就計劃了這樣的語法。定義constructor
函式和呼叫super(props)
一直是一種臨時解決方案,直到類欄位提供了一種符合人體工程學的替代方案。
但是讓我們回到這個例子,只使用ES2015特性:
class Checkbox extends React.Component {
construtor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
複製程式碼
為何我們要呼叫super
?我們可以不呼叫它嗎?如果我們不得不呼叫它,不傳props
引數會發生什麼呢?還存在其它的引數嗎? 讓我們一探究竟。
為何我們要呼叫 super?呼叫不當會發生什麼呢?
在JavaScript中,super
引用的是父類的constructor
(在我們的例子中,它指向React.Component
實現)。
重要的是,你不能使用this
直到你呼叫父級的constructor
後。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會強制父建構函式在你訪問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 forks!');
}
}
複製程式碼
想象一下this
在super
之前被呼叫是被允許的。一個月後,我們可能改變greetColleagues
中包含在person中的資訊:
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
複製程式碼
但是我們忘記了super()
在有機會設定this.name
之前呼叫了this.greetemployees()
,因此this.name
都還沒定義。正如你所看到的一樣,很難思考像這樣的程式碼。
為了避免這樣的坑,JavaScript會迫使你在使用this
之前先呼叫super
. 讓父級做它該做的事情!這個限制也適用於定義為類的React元件:
construtor(props) {
super(props);
// ✅ Okay to use `this` now
this.state = { isOn: true };
}
複製程式碼
這裡還存在另外一個問題:為什麼要傳遞props
?
你也許認為傳遞props
給super
是必要的,這樣React.Component
建構函式就能初始化this.props
了:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
複製程式碼
這與事實相去不遠——確實,事實就是如此。
但是,即使你呼叫super
沒帶props
引數,你依然可以在render
或者其它方法中獲取到props
。(如果你不信我,你可以自己試試!)
那到底是怎樣工作的呢?事實表明 React
也會在呼叫建構函式後立即在例項上初始化props
:
// Inside React
const instance = new YourComponent(props);
instance.props = props;
複製程式碼
因此即使你忘記了給super
傳遞props
,React
也會立即設定它們。這是有原因的。
當React新增了對類的支援時,它不僅僅只支援ES6中的類。目標是支援儘可能廣泛的類抽象。目前還不清楚使用ClojureScript
,CoffeeScript
,ES6
,Fable
,Scala.js
,TypeScript
,或者其它方式定義元件會有多成功。因此,React故意不明確是否需要呼叫super()
——即使ES6的類需要。
所以這就意味著你可以使用super()
代替super(props)
了嗎?
可能不是因為它仍然令人困惑。 當然,React會在你執行了constructor
之後為this.props
賦值。但是在super
呼叫和建構函式結束之間,this.props
仍然是未定義的:
// Inside React
class Component {
construtor(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
}
// ...
}
複製程式碼
如果在從建構函式呼叫的某個方法中發生這種情況,除錯可能會更加困難。這就是為什麼我總是建議大家傳super(props)
,儘管它不是必需的:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ We passed props
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
}
複製程式碼
保證在constructor
存在之前this.props
就被設定了。
還有最後一點React使用者可能會感到好奇的。
你可能注意到在類中使用Context API
時(包括舊的contextTypes
和在React16.6
中新增的現代contextType API
),context
作為第二個引數被傳給constructor
。
為什麼我們不用super(props, context)
代替呢?我們能夠,只是上下文的使用頻率較低,所以這個坑不會經常出現。
隨著類欄位的提議,這個坑基本上消失了。 如果沒有顯式建構函式,所有引數都將被自動傳遞下去。這就是允許像state ={}
這樣的表示式包含對this.props
或者this.context
的引用的原因。
使用Hooks
,我們甚至不會使用到super
或者this
。但是那是下次的話題。