核心本质:两种组件的底层实现逻辑不同
React 对类组件和函数组件的定位、执行方式从根源上就不一样,这直接决定了“是否有实例”:
- 类组件:是基于 ES6 类(Class)的实例化机制,每渲染一个类组件,就会创建一个类的实例,实例用来承载组件的状态、生命周期、方法等;
- 函数组件:本质就是普通的 JavaScript 函数,每次渲染都是一次“函数调用”,调用完就执行完毕,没有持久化的实例对象来承载状态(Hook 是通过 React 内部的“链表”单独管理状态,而非绑定到组件实例)。
1. 类组件:天生有实例(ES6 Class 特性)
React 类组件的本质是 继承自 React.Component 的 ES6 类,而 ES6 类的核心特性就是“实例化”:
(1)类组件的执行过程
jsx
// 类组件定义
class ClassInput extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' }; // 状态挂载在实例上
this.focus = this.focus.bind(this); // 方法绑定在实例上
}
focus() {
this.inputRef.focus(); // 通过实例访问 DOM ref
}
render() {
return <input ref={el => this.inputRef = el} />;
}
}
// 父组件使用
class Parent extends React.Component {
constructor(props) {
super(props);
this.classInputRef = React.createRef(); // 指向类组件实例
}
handleFocus() {
this.classInputRef.current.focus(); // 调用实例上的 focus 方法
}
render() {
// 渲染 ClassInput 时,React 会 new ClassInput(props) 创建实例
return (
<div>
<ClassInput ref={this.classInputRef} />
<button onClick={() => this.handleFocus()}>聚焦</button>
</div>
);
}
}
(2)类组件有实例的核心原因
- 实例是状态/方法的载体:类组件的
state、this.props、自定义方法(如focus)都挂载在实例上,没有实例就无法存储这些“持久化”的内容; - React 主动实例化:当渲染
<ClassInput />时,React 内部会执行new ClassInput(props),生成一个唯一的实例,这个实例会伴随组件的整个生命周期(挂载 → 更新 → 卸载); - ref 直接指向实例:给类组件绑
ref,ref.current就是这个实例,所以能直接调用实例上的方法(如focus)。
2. 函数组件:天生无实例(普通函数特性)
React 函数组件的本质是 接收 props 并返回 JSX 的普通函数,普通函数的执行特性是“调用即执行,执行完销毁”,没有“实例”的概念:
(1)函数组件的执行过程
jsx
// 函数组件定义
function FuncInput(props) {
const inputRef = useRef(null); // Hook 状态由 React 内部管理,而非实例
const focus = () => {
inputRef.current.focus();
};
return <input ref={inputRef} />;
}
// 父组件使用
function Parent() {
const funcInputRef = useRef(null); // 试图绑 ref 到函数组件
useEffect(() => {
console.log(funcInputRef.current); // undefined(无实例)
}, []);
return <FuncInput ref={funcInputRef} />; // 函数组件无实例,ref 赋值为 undefined
}
(2)函数组件无实例的核心原因
- 函数调用无持久化载体:每次渲染函数组件,都是一次普通的函数调用(
FuncInput(props)),函数执行完毕后,内部的变量(如inputRef、focus)会被纳入 React 的 Hook 管理体系,但函数本身不会留下任何“实例对象”; - React 不实例化函数组件:React 渲染函数组件时,只会执行函数、获取返回的 JSX,不会像类组件那样执行
new FuncInput(props),自然不会生成实例; - Hook 替代了实例的作用:函数组件的状态(
useState)、ref(useRef)、生命周期(useEffect)等,都由 React 内部通过“Hook 链表”来管理(每个函数组件对应的 Fiber 节点上挂载 Hook 链表),不需要依赖实例作为载体; - 设计初衷就是“无状态”:函数组件最初的设计目标是“纯函数组件”(无状态、无副作用),核心是“输入 props,输出 JSX”,实例会增加额外的内存开销,违背轻量设计。
3. 关键对比:实例的有无对开发的影响
| 特性 | 类组件 | 函数组件 |
|---|---|---|
| 实例存在性 | 有(ES6 Class 实例) | 无(普通函数调用,无实例) |
| 状态载体 | 实例(this.state) | React 内部 Hook 链表 |
| ref 指向 | 组件实例 | undefined(无 forwardRef 时) |
| 方法调用 | 通过实例调用(this.xxx) | 需通过 forwardRef + useImperativeHandle 暴露 |
总结
- 类组件有实例:因为类组件是 ES6 Class,React 渲染时会主动
new出实例,实例作为状态、方法的载体,ref 可直接指向实例; - 函数组件无实例:因为函数组件是普通函数,React 仅执行函数获取 JSX,无实例化过程,状态由 Hook 链表管理,而非实例,所以绑 ref 会得到 undefined;
- forwardRef 的作用:函数组件无实例,所以需要 forwardRef 把 ref “转发”到内部 DOM 节点/类组件实例,才能让父组件拿到可操作的目标。
简单记:类组件 = 有实例的“对象”,函数组件 = 无实例的“函数调用”,这是 React 两种组件模型最核心的区别之一。