深入理解 React 中 super(props) 的作用与必要性

作者:小积木时间:2026-10-22 20:59:36浏览:159

本文深入解析 React 类组件中 super(props) 的底层机制。解释 JavaScript 强制在访问 this 前调用父构造器的原因,以及 React 内部如何分配 props。探讨即使不传参也能访问 props 的原理,并说明为何最佳实践仍建议始终传递 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 增加对普通类的支持时,曾计划采用此类语法。定义constructor和调用super(props) 始终被视为一种临时解决方案,或许要等到类字段能提供在工程上更完善的替代方案时,这种写法才会被彻底取代。

不过,让我们回到前面的例子,这次仅使用 ES2015 的特性:


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

为什么必须调用 super?

为什么我们需要调用super?可以不调用它吗?如果必须调用,不传递prop参数会发生什么?还有其他参数吗?接下来我们进行尝试:

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

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


 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 };
 }

super(props) 中 props 参数的作用

这里又给我们留下了另一个问题:为什么要传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、ES6、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。

Context 参数的传递

最后一点是长期以来 React 用户总是感到好奇的。

你可能已经注意到,当你在类中使用 Context API 时(无论是旧版的contextTypes或在 React 16.6 中新添加的 contextType API),context 会作为第二个参数传递给构造函数。

那么为什么我们不写成super(props, context) 呢?我们可以这样做,但是使用context的频率较低,所以这个坑并没有那么多影响。

根据类字段提案的说明,这些坑大部分都会消失。如果没有显式构造函数,则会自动传递所有参数。这允许在像state = {} 这样的表达式中包含对this.props或this.context的引用(如果有必要的话)。

而有了 Hooks 之后,我们甚至不再有super或this。不过这是另外一个的话题了。

总结

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对本站的支持。如果你想了解更多相关内容请查看下面相关链接

作者最新文章
相关文章
更多