[第20期] 为何咱们要写 super(props) ?

转载自 https://overreacted.io/zh-han...

听说 Hooks 势头正盛,不过我仍是想略带调侃地从 class 的有趣之处开始这篇博客。可还行?javascript

这些梗对于使用 React 输出产品并不重要,但若是你想深刻的了解它们的运做原理,它们会很是的有用。前端

首先,在这一辈子中,super(props) 出如今我代码里的次数比我知道的还要多:java

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

固然了,咱们能够经过 class fields proposal 来省略这个声明:react

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

早在 2015 年 React 0.13 已经计划支持 。在当时,声明 constructor 和调用 super(props) 一直被视做暂时的解决方案,直到有合适的类字段声明形式。微信

但在此以前,咱们先回到 ES2015 风格的代码:函数

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

为何咱们要调用 super,咱们能够不这么作吗?那么在咱们调用它时不传入 props,又会发生什么呢?会有其余的缺省参数吗?接来下咱们就解开这一系列谜题。this

在 JavaScript 中,super 指的是父类(即超类)的构造函数。(在咱们的例子中,它指向了 React.Component 的实现。)spa

值得注意的是,在调用父类的构造函数以前,你是不能在 constructor 中使用 this 关键字的。JavaScript 不容许这个行为。调试

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴  还不能使用 `this`
    super(props);
    // ✅  如今能够了
    this.state = { isOn: true };
  }
  // ...
}

JavaScript 有足够合理的动机来强制你在接触 this 以前执行父类构造函数。考虑考虑一些类层次结构的东西:code

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!');
}

可是咱们并未想起 this.greetColleagues 在 super() 给 this.name 赋值前就已经执行。this.name 此时甚至还没有定义。能够看到,这样的代码难以往下推敲。

为了不落入这个陷阱,JavaScript 强制你在使用 this 以前先行调用 super。让父类来完成这件事情!:

constructor(props) {
  super(props);
  // ✅ 能使用 `this` 了
  this.state = { isOn: true };
}

这里留下了另外一个问题:为何要传入 props ?

你或许会想到,为了让 React.Component 构造函数可以初始化 this.props,将 props 传入 super 是必须的:

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

这几乎就是真相了 — 确然,它是 这样作 的。

但有些扑朔迷离的是,即使你调用 super() 的时候没有传入 props,你依然可以在 render 函数或其余方法中访问到 this.props。(若是你质疑这个机制,尝试一下便可)

那么这是怎么作到的呢?事实证实,React 在调用构造函数后也当即将 props 赋值到了实例上:**

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

所以即使你忘记了将 props 传给 super(),React 也仍然会在以后将它定义到实例上。这么作是有缘由的。

当 React 增长了对类的支持时,不只仅是为了服务于 ES6。其目标是尽量普遍地支持类抽象。当时咱们 不清楚 ClojureScript,CoffeeScript,ES6,Fable,Scala.js,TypeScript 等解決方案是如何成功的实践组件定义的。于是 React 刻意地没有显式要求调用 super() —— 即使 ES6 自身就包含这个机制。

这意味着你可以用 super() 代替 super(props) 吗?

最好不要,毕竟这样写在逻辑上并不明确确然,React 会在构造函数执行完毕以后给 this.props 赋值。但如此为之会使得 this.props 在 super 调用一直到构造函数结束期间值为 undefined。

// React 內部
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}
// 你的程式碼內部
class Button extends React.Component {
  constructor(props) {
    super(); // 😬 咱们忘了传入 props
    console.log(props);      // ✅ {}
    console.log(this.props); // 😬 未定义
  }
  // ...
}

若是在构造函数中调用了其余的内部方法,那么一旦出错这会使得调试过程阻力更大。这就是我建议开发者必定执行 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 更新的新版 contextTypes)的时候,context 是做为第二个参数传入构造函数的。

那么为何咱们不能转而写成 super(props, context) 呢?咱们固然能够,但 context 的使用频率较低,于是并无掘这个坑。

class fields proposal 出台后,这些坑大部分都会天然地消失在没有显示的定义构造函数的状况下,以上的属性都会被自动地初始化。这使得像 state = {} 这类表达式可以在须要的状况下引用 this.props 和 this.context 的内容。

然而,有了 Hooks 之后,咱们几乎就不须要 super 和 this 了。但那就是另外一个下午的茶点了。


前端收藏家(微信号: fedaily)
收集全网优秀前端技术资讯,与你分享,共同成长。

前端收藏家.jpg

相关文章
相关标签/搜索