Table of contents
Open Table of contents
React 为什么要区别对待类和函数组件?
const result = Greeting(props); // <p>Greeting</p>
const instance = new Greeting(props);
const result = instance.render(); // <p>Greeting</p>
- 函数组件:可以直接像普通函数一样传参调用,返回 UI 结构(React 元素)。
- 类组件:需要用
new实例化后,再调用实例对象的render方法返回 UI 结构。
如果类组件没有实例化会怎样?
- 如果源码(比如经 Babel 转译)被编译成构造函数: 不实例化直接调用会导致
this指向全局对象(如window或undefined),从而在访问this.props、this.state时抛出错误或导致意料之外的行为。 - 如果源码直接使用原生 ES6
class: 不使用new实例化直接调用会在语法层面报错(TypeError),因为 JavaScript 不允许直接调用 class 构造函数。
能否给所有组件统一使用 new 调用?
- 箭头函数组件:无法使用
new调用,原因是箭头函数没有 [[Construct]] 内部属性,JS 语言层面禁止用作构造器。 - 普通函数组件如果用 new 调用: 会返回一个对象,而不是 React 元素(比如:
new Greeting()得到的仅仅是一个对象,且 props 变成了实例属性),这和 React 渲染流程完全不兼容。
结论:
- 无论组件是否被 Babel 转译,类组件都必须用
new实例化; - 无论组件是否被 Babel 转译,函数组件都不能用
new调用,否则行为不符合预期。
React 是如何区别类和函数组件的?
React 依靠在基类 Component 的 prototype 上添加 isReactComponent 属性,实现对类组件和函数组件的区分:
class Component {}
Component.prototype.isReactComponent = {};
class Greeting extends Component {}
const isClassComponent = !!Greeting.prototype.isReactComponent; // true
- 如果某个组件的 prototype 上存在
isReactComponent属性,则该组件被认为是类组件。 - 函数组件没有 prototype 或其 prototype 上没有此属性,因此不会被误判为类组件。
补充说明:
- 组件无需依赖函数名、
render方法等实现细节,只需查找 prototype 上的唯一标识即可,设计简洁、高效、鲁棒性强。
为什么不这样做?
-
不用 instanceof 检查 Component,是因为:
- 当项目存在多个 React 版本(比如微前端或依赖冲突场景),不同 React 版本的 Component 构造函数不是同一个引用,导致
instanceof判断失效,不能可靠区分类组件。
- 当项目存在多个 React 版本(比如微前端或依赖冲突场景),不同 React 版本的 Component 构造函数不是同一个引用,导致
-
也不用检查 render 方法等属性,因为:
- render 属于实例方法,不一定能直接在 prototype 上检测到。
- 未来 React API 可能演化,不能保证类组件总有 render 方法。
- 实现上“少查一项”就是在性能、健壮性层面的加分。
基础知识
原型链
function Person() {}
Person.prototype.getName = function () {};
const person = new Person();
console.log(person.__proto__ === Person.prototype); // true
- 构造函数的 prototype 属性,正是其实例对象的
__proto__指向的对象。 - 原型链由实例的
__proto__不断向上查找父类 prototype,直到 Object.prototype 为止。这种机制让对象可继承父类(以及父类的父类……)的方法和属性。