这篇文章源自 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。