super() 和 super(props) 有什么区别?
一、是什么
super() 和 super(props) 都是 ES6 类继承中调用父类构造函数的语法,在 React 的类组件中有特定的含义。虽然这个话题在函数组件主导的今天看似过时,但它涉及 JavaScript 的核心继承机制,也是理解 React 类组件工作原理的基础知识。
要理解这个问题,需要先回顾 ES6 的 class 和 extends 机制。
二、ES6 类继承基础
super 的本质
在 ES6 中,super 关键字用于调用父类的构造函数。如果一个类使用 extends 继承了另一个类,那么在子类的 constructor 中必须先调用 super(),然后才能使用 this。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} 发出声音`);
}
}
class Dog extends Animal {
constructor(name, breed) {
// 必须先调用 super,否则报错
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name} 在叫`);
}
}
const dog = new Dog('旺财', '柴犬');
dog.speak(); // "旺财 发出声音"
dog.bark(); // "旺财 在叫"
不调用 super 会怎样
如果子类有 constructor 但没有调用 super(),JavaScript 会抛出 ReferenceError:
class Cat extends Animal {
constructor(name) {
// ❌ ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
this.name = name;
}
}
这是因为在 ES6 的继承模型中,子类的 this 对象是由父类的构造函数创建的(通过 super() 调用),子类只是在这个基础上进行修改。这与 ES5 基于原型链的继承方式有本质区别。
省略 constructor
如果子类不需要额外的初始化逻辑,可以完全省略 constructor。此时 JavaScript 引擎会自动生成一个默认的构造函数:
class Cat extends Animal {
// 等价于:
// constructor(...args) {
// super(...args);
// }
}
三、React 类组件中的 super
React.Component 的构造函数
React 的 Component 基类在构造函数中接收 props 并将其存储:
// React 源码简化版
class Component {
constructor(props) {
this.props = props;
this.state = {};
// ...
}
}
当我们创建一个类组件时:
class MyComponent extends React.Component {
constructor(props) {
super(props); // 将 props 传递给 Component 的构造函数
this.state = { count: 0 };
}
}
super(props) vs super()
两者的区别在于:super(props) 会将 props 传递给父类 React.Component 的构造函数,使得 this.props 在构造函数中可用。而 super() 不传参数,this.props 在构造函数中会是 undefined。
class WithProps extends React.Component<{ name: string }> {
constructor(props: { name: string }) {
super(props);
console.log(this.props); // ✅ { name: '张三' }
}
render() {
return <div>{this.props.name}</div>;
}
}
class WithoutProps extends React.Component<{ name: string }> {
constructor(props: { name: string }) {
super();
console.log(this.props); // ❌ undefined
console.log(props); // ✅ { name: '张三' }(参数仍然可用)
}
render() {
// ✅ render 中 this.props 正常,因为 React 在构造后会重新赋值
return <div>{this.props.name}</div>;
}
}
为什么 render 中 this.props 始终正常
即使在构造函数中使用 super() 而不是 super(props),this.props 在 render 和其他生命周期方法中仍然可以正常访问。这是因为 React 在调用构造函数之后,会立即对实例的 props 进行赋值:
// React 内部逻辑简化
const instance = new MyComponent(props);
instance.props = props; // React 在此处重新赋值
所以 super(props) 只影响构造函数内部能否通过 this.props 访问 props。
四、实际场景演示
构造函数中需要 this.props 的情况
class UserGreeting extends React.Component<{ name: string; isVip: boolean }> {
greeting: string;
constructor(props: { name: string; isVip: boolean }) {
super(props); // 必须传 props
// 在构造函数中需要使用 this.props
this.greeting = this.props.isVip
? `尊贵的 VIP 用户 ${this.props.name},欢迎回来!`
: `${this.props.name},你好!`;
this.state = {
lastLogin: new Date(),
};
}
render() {
return <h1>{this.greeting}</h1>;
}
}
不需要构造函数中访问 this.props
class Counter extends React.Component<{}, { count: number }> {
constructor(props: {}) {
super(); // 这里不传 props 也没问题
this.state = { count: 0 }; // state 初始化不依赖 props
}
render() {
return <div>{this.state.count}</div>;
}
}
使用类属性语法——根本不需要 constructor
现代 TypeScript/Babel 支持类属性语法,可以完全避开构造函数:
class ModernCounter extends React.Component<
{ initialCount: number },
{ count: number }
> {
// 类属性语法初始化 state
state = {
count: this.props.initialCount,
};
// 箭头函数属性自动绑定 this
handleClick = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
类属性语法在编译时实际上会被转换到构造函数内,但编译器会自动处理 super(props) 的调用,开发者无需操心。
五、最佳实践
类组件中始终使用 super(props)
为了避免在构造函数中因遗忘传递 props 而产生潜在的 bug,推荐统一使用 super(props):
class MyComponent extends React.Component<MyProps, MyState> {
constructor(props: MyProps) {
super(props); // ✅ 始终传递 props
this.state = { /* ... */ };
}
}
更好的做法——避免使用构造函数
// ✅ 使用类属性语法,不需要构造函数
class MyComponent extends React.Component<MyProps, MyState> {
state: MyState = {
count: 0,
data: null,
};
handleClick = () => {
// ...
};
render() {
return <div>...</div>;
}
}
最佳做法——使用函数组件
// ✅✅ 函数组件完全不存在 super 的问题
function MyComponent({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
}
六、常见面试追问
Q:如果不写 constructor,React 是怎么处理的?
A:如果类组件没有定义 constructor,JavaScript 引擎会使用默认构造函数 constructor(...args) { super(...args); },这等效于 super(props),所以一切正常。
Q:super() 不传 props 会导致 bug 吗?
A:在绝大多数情况下不会。this.props 在构造函数以外的地方始终可以正确访问,因为 React 在构造后会重新赋值。只有在构造函数中同时需要 this.props 且使用了 super() 时,才会遇到 this.props 为 undefined 的问题。
Q:函数组件为什么不需要 super?
A:函数组件就是普通函数,不涉及类继承。props 作为函数参数直接传入,没有 this,也就不存在 super 的问题。这也是函数组件心智模型更简单的原因之一。
七、知识脉络
ES6 class 继承
└── super() 调用父类构造函数
├── super() → 不传参数,this.props 在 constructor 中为 undefined
└── super(props) → 传递 props,this.props 在 constructor 中可用
│
├── React 内部会在构造后重新赋值 instance.props = props
│ └── 所以 render/生命周期中 this.props 始终正常
│
├── 类属性语法可以避开手写 constructor
│
└── 函数组件完全不存在这个问题
└── 这也是 React 推荐函数组件的原因之一
八、总结
super() 和 super(props) 的区别本质上是 ES6 继承机制的问题,而不是 React 特有的。在现代 React 开发中,函数组件 + Hooks 已经完全取代了类组件的使用,这个问题更多是作为理解 JavaScript 继承机制和 React 历史的知识点。面试中如果被问到,除了回答技术细节,还应该补充说明函数组件已经消除了这类问题。