类组件与函数式组件深度对比
约 1407 字大约 5 分钟
布欧-Lewyon
2026-05-15
首页 › React › 类组件到 Hooks 迁移 › 类组件与函数式组件深度对比
本节从源码原型链层面,深入对比 Class Component 和 Function Component 的差异,帮助你理解为什么 React 社区最终选择了 Hooks。
底层实现差异
创建 Fiber 时的区别
React 内部通过 tag 区分两种组件:
// 源码 packages/react-reconciler/src/ReactWorkTags.js
export const FunctionComponent = 0;
export const ClassComponent = 1;在 beginWork 中:
function beginWork(current, workInProgress) {
switch (workInProgress.tag) {
case FunctionComponent: {
return updateFunctionComponent(current, workInProgress);
}
case ClassComponent: {
return updateClassComponent(current, workInProgress);
}
}
}函数组件的执行
// 简化版 updateFunctionComponent
function updateFunctionComponent(current, workInProgress, Component, nextProps) {
// 1. 准备 Hooks 环境
nextChildren = renderWithHooks(current, workInProgress, Component, nextProps);
// 2. Diff 子节点
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}函数组件本质就是:执行函数,拿到返回值(ReactElement)。
类组件的执行
// 简化版 updateClassComponent
function updateClassComponent(current, workInProgress, Component, nextProps) {
// 1. 获取或创建实例
const instance = workInProgress.stateNode;
if (instance === null) {
// 首次挂载:new Component(props)
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps);
}
// 2. 调用 shouldComponentUpdate(纯组件检查)
if (instance.shouldComponentUpdate) {
// ...
}
// 3. 调用 render 方法
nextChildren = instance.render();
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}类组件多出了:创建 instance → 调用生命周期 → render() 的步骤。
原型链解析
类组件的原型链
class MyComponent extends React.Component {
render() {
return <div>Hello</div>;
}
}
const instance = new MyComponent({ name: 'React' });原型链:
instance
→ MyComponent.prototype // 包含 render(), 自定义方法
→ React.Component.prototype // 包含 setState(), forceUpdate()
→ Object.prototypesetState 定义在 React.Component.prototype 上:
// 源码 packages/react/src/ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater; // React 内部注入的 updater 对象
}
Component.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};类组件的 this 可以持久化状态——因为 instance 在组件卸载前一直存在,this.state 和 this.props 都挂在实例上。
函数组件的本质
函数组件没有 this,没有 instance:
function MyComponent(props) {
return <div>Hello</div>;
}每次渲染,React 只是调用这个函数,没有 new 操作:
// renderWithHooks 内部
const children = Component(props); // 就是调函数,没有 new!函数组件的 state 保存在 Fiber 节点的 memoizedState 上,而非实例属性:
渲染行为差异
每次渲染都是独立的闭包
函数组件每次 render 都创建一个新闭包:
function Counter() {
const [count, setCount] = useState(0);
function handleAlert() {
setTimeout(() => {
alert(count); // ⚡ 点击时的 count 值,不是最新的
}, 3000);
}
return (
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={handleAlert}>弹窗</button>
);
}类组件则通过 this 指向同一个实例:
class Counter extends React.Component {
state = { count: 0 };
handleAlert = () => {
setTimeout(() => {
alert(this.state.count); // ⚡ 3 秒后的最新值
}, 3000);
};
render() {
return (
<>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>+1</button>
<button onClick={this.handleAlert}>弹窗</button>
</>
);
}
}原因分析
| 类组件 | 函数组件 | |
|---|---|---|
| 状态存储 | this.state(实例属性,可变) | useState 返回值(闭包,不可变) |
| 读取方式 | this.state.count → 永远指向最新 | count → 当前渲染快照 |
| ref 状态 | 实例一直存在 | 用 useRef 可以绕过闭包限制 |
在函数组件中获取最新值
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
// 同步 ref
useEffect(() => { countRef.current = count; }, [count]);
function handleAlert() {
setTimeout(() => {
alert(countRef.current); // ✅ 通过 ref 读取最新值
}, 3000);
}
// ...
}性能差异
类组件的额外开销
类组件在以下方面有额外消耗:
- 创建实例:
new Component()和原型链查找 - 生命周期调用:mount/update 时检查和调用多个生命周期
this绑定:需要额外处理事件绑定(bind / 箭头函数)
函数组件的优势
| 指标 | 类组件 | 函数组件 |
|---|---|---|
| 包体积(gzip) | 更大(含 Component 基类) | 更小 |
| 初始化时间 | 需 new 实例 | 直接调函数 |
| 代码量 | 更多模板代码 | 更简洁 |
| 拆分粒度 | 按生命周期组织 | 按关注点拆分(自定义 Hook) |
实际性能对比
React 官方在 18 版本后,函数组件没有明显的性能劣势。对于大规模应用,函数组件的优势更偏向于人效——更少的代码、更清晰的逻辑组合。
可测试性对比
// 函数组件测试
test('Counter increments', () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
// 类组件测试
test('Counter increments', () => {
const wrapper = shallow(<Counter />);
wrapper.find('button').simulate('click');
expect(wrapper.state('count')).toBe(1);
});函数组件的 Hooks 可以被 renderHook 独立测试,无需渲染整个组件树。类组件的状态测试则必须依赖组件实例或 shallow render。
迁移对照表
| 类组件能力 | 函数组件等价方案 |
|---|---|
this.state | useState / useReducer |
setState(prev => newState) | setState(prev =› newState) 或 dispatch |
this.setState({}, callback) | useEffect(依赖 state 变化) |
componentDidMount | useEffect(() => {...}, []) |
componentDidUpdate(prevProps) | useEffect(() => {...}, [dep]) |
componentWillUnmount | useEffect(() => { return () => {...} }, []) |
shouldComponentUpdate | React.memo |
getSnapshotBeforeUpdate | useLayoutEffect + ref |
componentDidCatch | ErrorBoundary(目前只能用类组件) |
getDerivedStateFromProps | 直接计算或 useMemo |
this.xxx 实例方法 | useRef + useImperativeHandle |
forceUpdate | 用 useReducer 触发 dispatch 空操作 |
小结
| 对比维度 | 类组件 | 函数组件 |
|---|---|---|
| 底层机制 | 实例化 + 原型链 | 闭包 + Fiber.memoizedState |
| 状态存储 | this.state(实例属性) | Hook 链表(Fiber 节点上) |
| 每次渲染 | 复用同一个 this | 创建新闭包 |
| 代码组织 | 按生命周期切割 | 按关注点切割(自定义 Hook) |
| 打包体积 | 更大(继承基类) | 更小 |
| 测试 | 需要 mount 组件 | Hooks 可独立测试 |
| 官方推荐 | 新项目不建议 | ✅ 首选 |
上一节:完整迁移案例 下一节:React 16 → 17 渐进式升级
