原文:overreacted.io/why-do-we-w…javascript
我据说 Hooks 最近很火。讽刺的是,我想用一些关于 class 组件的有趣故事来开始这篇文章。你以为如何?html
本文中这些坑对于你正常使用 React 并非很重要。 可是假如你想更深刻的了解它的运做方式,就会发现实际上它们颇有趣。java
开始第一个。react
首先在个人职业生涯中写过的 super(props)
本身都记不清:git
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
复制代码
固然,在类字段提案 (class fields proposal) 中建议让咱们跳过这个开头:github
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
复制代码
在2015年 React 0.13 增长对普通类的支持时,曾经打算用这样的语法。定义 constructor
和调用 super(props)
始终是一个临时的解决方案,可能要等到类字段可以提供在工程学上不那么反人类的替代方案。typescript
不过仍是让咱们回到前面这个例子,此次只用ES2015的特性:ide
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
复制代码
为何咱们要调用super
? 能够调用它吗? 若是必需要调用,不传递prop
参数会发生什么? 还有其余参数吗? 接下来咱们试一试:函数
在 JavaScript 中,super
指的是父类的构造函数。(在咱们的示例中,它指向React.Component
的实现。)ui
重要的是,在调用父类构造函数以前,你不能在构造函数中使用this
。 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!');
}
}
复制代码
若是容许在调用 super
以前使用this
的话。一段时间后,咱们可能会修改 greetColleagues
,并在提示消息中添加Person
的name
:
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
复制代码
可是咱们忘记了 super()
在设置 this.name
以前先调用了 this.greetColleagues()
。 因此此时 this.name
尚未定义! 如你所见,像这样的代码很难想到问题出在哪里。
为了不这类陷阱,JavaScript 强制要求:若是想在构造函数中使用this
,你必须首先调用super
。 先让父类作完本身的事! 这种限制一样也适用于被定义为类的 React 组件:
constructor(props) {
super(props);
// ✅ 在这里能够用 `this`
this.state = { isOn: true };
}
复制代码
这里又给咱们留下了另外一个问题:为何要传 props
参数?
你可能认为将props
传给super
是必要的,这可使 React.Component
的构造函数能够初始化this.props
:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
复制代码
这与正确答案很接近了 —— 实际上它就是这么作的。
可是不知道为何,即使是你调用 super
时没有传递 props
参数,仍然能够在 render
和其余方法中访问this.props
。 (不信你能够亲自去试试!)
这是到底是为何呢? 事实证实,在调用构造函数后,React也会在实例上分配 props
:
// Inside React
const instance = new YourComponent(props);
instance.props = props;
复制代码
所以,即便你忘记将props
传给 super()
,React 仍然会在以后设置它们。 这是有缘由的。
当 React 添加对类的支持时,它不只仅增长了对 ES6 类的支持。它的目标是尽量普遍的支持类抽象。 目前还不清楚 ClojureScript、CoffeeScript、ES六、Fable、Scala.js、TypeScript或其余解决方案是如何相对成功地定义组件的。 因此 React 故意不关心是否须要调用 super()
—— 即便是ES6类。
那么这是否是就意味着你能够写 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
}
// ...
}
复制代码
若是这种状况发生在从构造函数调用的某个方法中,可能会给调试工做带来很大的麻烦。 这就是为何我建议老是调用 super(props)
,即便在没有必要的状况之下:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ 传递了 props 参数
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
复制代码
这样就确保了可以在构造函数退出以前设置好 this.props
。
最后一点是长期以来 React 用户老是感到好奇的。
你可能已经注意到,当你在类中使用Context API时(不管是旧版的 contextTypes
或在 React 16.6中新添加的 contextType
API),context
会做为第二个参数传递给构造函数。
那么为何咱们不写成 super(props, context)
呢? 咱们能够这样作,可是使用 context
的频率较低,因此这个坑并无那么多影响。
根据类字段提案的说明,这些坑大部分都会消失。 若是没有显式构造函数,则会自动传递全部参数。 这容许在像 state = {}
这样的表达式中包含对 this.props
或 this.context
的引用(若是有必要的话)。
而有了 Hooks 以后,咱们甚至再也不有 super
或 this
。 不过这是另一个的话题了。