谈谈super(props) 的重要性

翻译:疯狂的技术宅
原文: https://overreacted.io/why-do...

本文首发微信公众号:jingchengyideng
欢迎关注,天天都给你推送新鲜的前端技术文章javascript


我据说 Hooks 最近很火。讽刺的是,我想用一些关于 class 组件的有趣故事来开始这篇文章。你以为如何?html

本文中这些坑对于你正常使用 React 并非很重要。 可是假如你想更深刻的了解它的运做方式,就会发现实际上它们颇有趣。前端

开始第一个。java


首先在个人职业生涯中写过的 super(props) 本身都记不清:react

class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}

固然,在类字段提案 (class fields proposal) 中建议让咱们跳过这个开头:git

class Checkbox extends React.Component {
  state = { isOn: true };
  // ...
}

在2015年 React 0.13 增长对普通类的支持时,曾经打算用这样的语法。定义 constructor 和调用 super(props) 始终是一个临时的解决方案,可能要等到类字段可以提供在工程学上不那么反人类的替代方案。github

不过仍是让咱们回到前面这个例子,此次只用ES2015的特性:typescript

class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}

为何咱们要调用super? 能够调用它吗? 若是必需要调用,不传递prop参数会发生什么? 还有其余参数吗? 接下来咱们试一试:微信


在 JavaScript 中,super 指的是父类的构造函数。(在咱们的示例中,它指向React.Component 的实现。)ide

重要的是,在调用父类构造函数以前,你不能在构造函数中使用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,并在提示消息中添加Personname

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.propsthis.context 的引用(若是有必要的话)。

而有了 Hooks 以后,咱们甚至再也不有 superthis 。 不过这是另一个的话题了。


本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,天天推送我翻译的技术文章欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章