React 为什么要写 super(props)?

突然看到 dan 的第一篇 blog,解释为什么 react 需要写 super(props),为后面 hooks 不需要 super 和 this 作铺垫。
本文也会对比下 Omi 框架, 解释下 Omi 为什么从一开始就不需要写 constructorsuper, 更不需要传递 props, 使用 install 足够,而且 install 里可以直接使用 this.props

原文地址

我听说 Hooks 最近很火热。具有讽刺意味的是,我想通过描述关于类组件的有趣事实来开始这个博客。怎么样?

这些 gotchas 对于使用 React 并不重要。但如果你喜欢深入研究事物是如何运作的,你可能会发现它们的有趣之处。

这是第一个。


  1. class Checkbox extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { isOn: true };
  5. }
  6. // ...
  7. }

当然你可以是用 class fields proposal

  1. class Checkbox extends React.Component {
  2. state = { isOn: true };
  3. // ...
  4. }

当React 0.13在2015年增加了对普通类的支持时,就计划使用这样的语法。定义构造函数和调用super(props)始终是临时解决方案,直到类字段提供了替代方案。

但是让我们回到这个例子,只使用 ES2015 特性:

  1. class Checkbox extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { isOn: true };
  5. }
  6. // ...
  7. }

为什么要调用 super,可以不调用它吗?如果我们必须调用它,我们不传 props 会发生什么?还有其他参数吗?接着往下看:


在JavaScript中,super 指向父类构造函数。(在我们的例子中,它指向了React.Component )。

重要的是,在调用父构造函数之后,才能在构造函数中使用 this。

  1. class Checkbox extends React.Component {
  2. constructor(props) {
  3. // ? Can’t use `this` yet
  4. super(props);
  5. // ✅ Now it’s okay though
  6. this.state = { isOn: true };
  7. }
  8. // ...
  9. }

在触及这个之前,JavaScript强制父构造函数运行是有原因的。考虑类层次结构:

  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. }
  6. class PolitePerson extends Person {
  7. constructor(name) {
  8. this.greetColleagues(); // ? 这是不允许的,为什么请往下看
  9. super(name);
  10. }
  11. greetColleagues() {
  12. alert('Good morning folks!');
  13. }
  14. }

想象一下如果 super 之前可以使用 this。一个月后,我们可能会更改 greetColleagues,在消息中包括此人的姓名:

  1. greetColleagues() {
  2. alert('Good morning folks!');
  3. alert('My name is ' + this.name + ', nice to meet you!');
  4. }

这样的话,调用就出错了,调用 greetColleagues 的时候 this.name 从未定义。

所以js规定使用this之前必须调用 super,在react当中:

  1. constructor(props) {
  2. super(props);
  3. // ✅ Okay to use `this` now
  4. this.state = { isOn: true };
  5. }

那么,只剩下为什么传 props


您可能认为将 props 传递到super是必要的,以便 React.Component 构造函数可以初始化 this.props:

  1. // Inside React
  2. class Component {
  3. constructor(props) {
  4. this.props = props;
  5. // ...
  6. }
  7. }

这离真相不远——事实上,这就是它所做的

但即使调用 super() 时没有 props 参数,您仍然可以在 render 和其他方法中访问 this.props。(如果你不相信我,你自己试试!)

怎么做到的?答案如下:

  1. // Inside React
  2. const instance = new YourComponent(props);
  3. instance.props = props;

所以,即使你忘记把道具传给super(),React 仍然会在事后正确设置。这是有原因的。

这是否意味着你可以只写 super() 而不写 super(props)?

可能不是因为它仍然令人困惑。当然,在构造函数运行之后,React 会设置 props 。但是,在 super 和构造函数的末尾之间,仍然没有定义 this.props:

  1. // Inside React
  2. class Component {
  3. constructor(props) {
  4. this.props = props;
  5. // ...
  6. }
  7. }
  8. // Inside your code
  9. class Button extends React.Component {
  10. constructor(props) {
  11. super(); // ? We forgot to pass props
  12. console.log(props); // ✅ {}
  13. console.log(this.props); // ? undefined
  14. }
  15. // ...
  16. }

如果这种情况发生在从构造函数调用的一些方法中,那么调试带来疑惑。这就是为什么我建议总是 super(props),即使它不是严格必要的:

  1. class Button extends React.Component {
  2. constructor(props) {
  3. super(props); // ✅ We passed props
  4. console.log(props); // ✅ {}
  5. console.log(this.props); // ✅ {}
  6. }
  7. // ...
  8. }

您可能已经注意到,当在类中使用Context API时(React 16.6中添加的 Context API),Context 作为第二个参数传递给构造函数。

那么我们为什么不写 super(props, context) 替代, 但是 context 很少被使用,所以这个困扰就没有那么多了。

随着 class fields proposal 的应用,没有 constructor, 所有参数会自动传递下去.

使用 Hooks, 我们甚至不需要 super 和 this,这是另一个话题。

译者扩展

在 Omi 框架的源码中, 常用的两个类有 WeElementModelView

  1. class WeElement extends HTMLElement {
  2. constructor() {
  3. super()
  4. ...
  5. }
  6. }
  1. class ModelView extends WeElement {
  2. static observe = true
  3. beforeInstall() {
  4. this.data = this.vm.data
  5. }
  6. observed() {
  7. this.vm.data = this.data
  8. }
  9. }

类的构造函数 constructor 总是先调用 super() 来建立正确的原型链继承关系。HTMLElement 继承自父接口 Element 和 GlobalEventHandlers 的属性,所以一些节点增删改查的操作以及时间监听和绑定都是通过 super 建立起来。

对 Omi 框架的使用者,完全不需要 constructor、super等。props 和 data 以及 store 都会在内部注入进去。而且是在生命周期勾子 install 之前就注入了,所以你可以在 install 的时候使用 this.props this.data 甚至 this.store(如果从根节点注入了 store 的话)。

  1. import { render, WeElement, define } from 'omi'
  2. define('my-counter', class extends WeElement {
  3. static observe = true
  4. data = {
  5. count: 1
  6. }
  7. sub = () => {
  8. this.data.count--
  9. }
  10. add = () => {
  11. this.data.count++
  12. }
  13. render() {
  14. return (
  15. <div>
  16. <button onClick={this.sub}>-</button>
  17. <span>{this.data.count}</span>
  18. <button onClick={this.add}>+</button>
  19. </div>
  20. )
  21. }
  22. })
  23. render(<my-counter />, 'body')

或者:

  1. import { define, render } from 'omi'
  2. define('my-counter', function() {
  3. const [count, setCount] = this.use({
  4. data: 0,
  5. effect: function() {
  6. document.title = `The num is ${this.data}.`
  7. }
  8. })
  9. this.useCss(`button{ color: red; }`)
  10. return (
  11. <div>
  12. <button onClick={() => setCount(count - 1)}>-</button>
  13. <span>{count}</span>
  14. <button onClick={() => setCount(count + 1)}>+</button>
  15. </div>
  16. )
  17. })
  18. render(<my-counter />, 'body')

如果没有 effect,可以直接使用 useData:

  1. const [count, setCount] = this.useData(0)