讲道理,React中,我们为什么需要写 super(props)?

4,752 阅读4分钟

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