我聽說 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.name
的super
方法被呼叫之前,已經呼叫了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;
複製程式碼
所以即使忘記傳props
給super
,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中新增的contextType
API,context
都會做為constructor的第二個引數用來傳遞)。
所以我們為什麼不用super(props, context)
來替代呢?其實我們可以,但 context 的使用頻率較低,所以遇到的坑沒有那麼多。
當有了class fields proposal,大部分的坑都會消失。在沒有標明constructor的情況下,全部引數會被自動傳入。這樣就允許像state = {}
的表示式,如果有需要this.props
或者this.context
將同樣適用。
Hooks中,我們甚至不需要super
或者this
。但這要改天再說。