這篇文章源自 Dan 的部落格。
現在的熱點是 hooks,所以 Dan 決定寫一篇關於 class
元件的文章 ?。
文章中描述的問題,應該不會影響你寫程式碼;不過如果你想深入研究 React 是怎麼工作的,這篇文章可能會對你有幫助。
第一個問題:
我自己都不知道我寫了多少遍 super(props)
:
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
複製程式碼
當然,class 屬性提案 可以簡化程式碼:
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
複製程式碼
2015 年初的時候,React 0.13 版本就已經計劃支援該語法。在此之前,我們需要不斷地寫 constructor
,然後再呼叫 super(props)
。
現在我們先回顧一下之前的寫法:
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
複製程式碼
我們為什麼要呼叫 super
?能不能不呼叫?如果呼叫的時候不傳入props
呢?還可以傳入其他引數麼?帶著這些問題往下看。
在 JavaScript 中,super
引用的是父類建構函式。(在 React 中,引用的自然就是 React.Component
)
需要注意的是,在呼叫父類建構函式之前,無法用 this
。其實這不是 React 的限制,而是 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!`);
}
}
複製程式碼
如果 JavaScript 允許在呼叫 super
之前使用 this
,一個月之後,我們需要修改 greetColleagues
方法,方法中使用了 name 屬性:
greetColleagues() {
alert(`Good morning folks!`);
alert(`My name is ` + this.name + `, nice to meet you!`);
}
複製程式碼
不過我們可能已經忘了 this.greetColleagues();
是在呼叫 super
之前呼叫的;因此,this.name
還沒有賦值。這樣的程式碼,很難定位 bug。
為了避免這樣的錯誤,JavaScript 強制開發者在建構函式中先呼叫 super
,才能使用this
。這一限制,也被應用到了 React 元件:
constructor(props) {
super(props);
// ✅ 現在可以用 `this` 啦
this.state = { isOn: true };
}
複製程式碼
問題又來了:為什麼要傳入 props
呢?
你可能以為必須給 super
傳入 props
,否則 React.Component
就沒法初始化 this.props
:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
複製程式碼
en…離真相不遠 —— 事實上,React 也的確這麼幹了。
不過,如果你不小心漏傳了 props
,直接呼叫了 super()
,你仍然可以在 render
和其他方法中訪問 this.props
(不信的話可以試試嘛)。
為啥這樣也行?因為React 會在建構函式被呼叫之後,會把 props
賦值給剛剛建立的例項物件:
// Inside React
const instance = new YourComponent(props);
instance.props = props;
複製程式碼
props
不傳也能用,是有原因的。
React 新增對 class 支援的時候,不僅僅要支援 ES6 的 class,還需要考慮其他的 class 實現, CoffeeScript, ES6, Fable, Scala.js, TypeScript 中 class 的使用方式並不一致。所以,即使有了 ES6 class,在呼叫 super()
這個問題上,React 沒做太多限制。
但這意味著你在使用 React 時,可以用 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
}
// ...
}
複製程式碼
要是建構函式中呼叫了某個訪問 props
的方法,那這個 bug 就更難定位了。因此我強烈建議始終使用super(props)
,即使這不是必須的:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ We passed props
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
複製程式碼
上面的程式碼確保 this.props
始終是有值的。
還有一個問題可能困擾 React 開發者很久了。你應該已經注意到,當你在 class 中使用 Context API 時(無論是之前的 contextTypes
還是現在的 contextType
API),context
都是作為建構函式的第二個引數。
我們為什麼不用寫 super(props, context)
?我們當然可以這麼寫,不過 context API 用的相對較少,所以引發的 bug 也比較少。
感謝class 屬性提案 ,這樣的 bug 幾近絕跡。只要沒有顯式宣告建構函式,所有引數都會被自動傳遞。所以,在state = {}
表示式中,你可以訪問this.props
以及 this.context
。