我听说 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中添加的contextTypeAPI,context都会做为constructor的第二个参数用来传递)。
所以我们为什么不用super(props, context)来替代呢?其实我们可以,但 context 的使用频率较低,所以遇到的坑没有那么多。
当有了class fields proposal,大部分的坑都会消失。在没有标明constructor的情况下,全部参数会被自动传入。这样就允许像state = {}的表达式,如果有需要this.props或者this.context将同样适用。
Hooks中,我们甚至不需要super或者this。但这要改天再说。