返回博客归档

ISSUE / 005

[译]Why Do We Write super(props)?

Why Do We Write super(props)? 为什么我们要写 super(props) ? 原文:https://overreacted.io/why-do-we-write-super-props/ 备注:自个儿翻译的,翻译如有错误,欢迎指出 听闻最近Hooks相当火 但是今天要分享的还…

树影落在墙面上的编辑照片

Why Do We Write super(props)? 为什么我们要写 super(props) ?

原文:https://overreacted.io/why-do-we-write-super-props/
备注:自个儿翻译的,翻译如有错误,欢迎指出~

听闻最近Hooks相当火~ 但是今天要分享的还是有关于类组件的东西,因吹斯听!

以下内容并不会让你写react更六,但是如果想对这其中的原理感兴趣的话,这会非常有趣。

这是首先要说的事


我这一生写过的super(props)比我吃的米还要多:

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

当然,class fields proposal特性可以让我们的代码更简洁:

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

2015年的时候,那时 React 0.13 版本增加了对class的支持,我们计划了这种写法。定义一个constructor和调用super(props)一直是一个临时的解决方案直到class field提供了一个更贴合语义的方案。

但是让我们再回过头看看这段代码(no class field):

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

为什么我们要调用super? 我们可以不调用它吗?如果必须要调用,那不传props会发生什么?还可以传其他的参数吗? 欢迎收看本期走进科学~

在JavaScript中,super指代的的是父类的构造函数。(在我们的例子中,super指的是React.Component

有一点很重要,JavaScript不允许你在super调用之前使用this

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴 Can’t use `this` yet
    super(props);
    // ✅ Now it’s okay though
    this.state = { isOn: true };
  }
  // ...
}

JavaScript不允许你在调用父类的构造函数之前使用this是有原因的。让我们看下这个例子:

class Person {
  constructor(name) {
    this.name = name;
  }
}

class PolitePerson extends Person {
  constructor(name) {
    this.greetColleagues(); // 🔴 This is disallowed, read below why
    super(name);
  }
  greetColleagues() {
    alert('Good morning folks!');
  }
}

想象一下,如果在调用super之前允许使用this。一个月以后,我们可能改变了greetColleagues的实现,如下:

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

但是我们忘记了一点,那就是this.greetColleagues()在super调用前(设置this.name)被调用。这就意味着this.name甚至可能还未定义!正如我们所看到的,这样的代码看起来让人容易产生困惑。

为了避免这样的错误,JavaScript禁止在调用super前使用this。让父类的构造函数做它该做的事!这个限制同样适用于定义为类组件的类:

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

这给我们留下了另一个问题:为什么要传递props


你可能认为传递propssuper是因为为了让React.Component的构造函数能使用传递的props进行初始化:

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

这与真相只有一步之遥 -- 事实上,这才是真相

但是你可以试一下,即使你调用super()没传递props参数,你仍然能够在render和其他方法中拿到this.props!amazing~

这是怎么回事?事实上,React还会在你调用完构造函数之后再一次将props挂载到实例的属性上

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

因此,即使你忘记了传递propssuper,React仍然会对此做修正,这是有原因的。

当React支持类组件时,不仅仅只考虑支持ES6的class。目标是尽可能地支持已有的class实现,尚且不清楚最后ClojureScript,CoffeeScript,ES6, Fable, Scala.js, TypeScript或者其他的定义类组件的方案会有多成功。所以React是有意设计不需要调用super()的。尽管ES6的class需要。

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

答案可能是不可以! 当然,React确实会在构造函数之后再次赋值this.props。但是this.propssuper()和构造函数结束之间仍然是undefined:

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

// Inside your code
class Button extends React.Component {
  constructor(props) {
    super(); // 😬 We forgot to pass props
    console.log(props);      // ✅ {}
    console.log(this.props); // 😬 undefined 
  }
  // ...
}

如果你在构造函数中调用了类的其他方法,这就有可能给调试带来更多的麻烦。所以我还是推荐总是传递props来调用super(props),尽管这不是必须的。

class Button extends React.Component {
  constructor(props) {
    super(props); // ✅ We passed props
    console.log(props);      // ✅ {}
    console.log(this.props); // ✅ {}
  }
  // ...
}

这确保了一点,this.props在super之后构造函数结束之前是有值的。


还有最后一点可能是React忠实粉丝可能会好奇的。

你可能会注意到当你使用Context API在类组件中(不管是旧的contextTypes还是在React 16.6 新添加的ContexTypeAPI),context都作为构造函数的第二个参数被传递。

那为什么我们不写super(props, context)代替呢?当然我们可以这样写,但是因为context使用的不是很频繁,所以这个隐患并没有出现太多。

随着class field提案的提出,这个隐患基本可以说消除了。
没有显式的声明构造函数,所有的参数都将自动传递。这允许像state = {}的表达式直接引用this.propsthis.context

有了Hooks,我们甚至不再需要superthis了,但这又是另外的一个话题了。