一、 核心定义与本质区别
这是两者最基础的差异,决定了后续所有特性的不同:
| 特性 | 类组件(Class Component) | 函数组件(Functional Component) |
|---|---|---|
| 本质类型 | 基于 ES6 Class 语法,继承 React.Component/React.PureComponent | 基于 JavaScript 普通函数/箭头函数,无继承关系 |
| 组件实例 | 有自身实例(this 指向组件实例对象) | 无组件实例(不存在 this 指向,无需处理 this 绑定问题) |
| 核心定位(历史演变) | 早期 React 主流组件,提供完整状态和生命周期能力 | 早期为「无状态组件」(仅接收 props 渲染视图),React 16.8 Hooks 推出后成为主流,具备完整能力 |
| 最简结构 | 必须包含 render() 方法返回 JSX | 直接返回 JSX(无需 render 关键字) |
最简示例对比
jsx
// 类组件
class ClassGreeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>; // 依赖 this 访问 props
}
}
// 函数组件
const FunctionGreeting = (props) => {
return <h1>Hello, {props.name}</h1>; // 直接访问 props 参数,无 this
};
二、 状态管理能力差异
状态管理是组件的核心能力之一,两者的实现方式、语法特性差异显著:
| 特性 | 类组件 | 函数组件(Hooks) |
|---|---|---|
| 状态管理 API | 基于 this.state(状态对象)和 this.setState()(状态更新方法) | 基于 useState(简单状态)/ useReducer(复杂状态)Hook |
| 状态初始化 | 两种方式:1. 构造函数 constructor 中初始化;2. 类体直接赋值(ES7 语法) | 直接在组件内部调用 useState,支持惰性初始化(传入函数避免复杂计算重复执行) |
| 状态更新特性 | setState 是异步的,自动合并对象类型状态(仅合并顶层属性) | useState 的更新函数是异步的,替换式更新(对象/数组状态需手动合并原有值) |
| 依赖当前状态更新 | 可直接使用 this.state(需注意异步陷阱),也支持函数式更新 | 推荐使用函数式更新(传入 prevState 参数),避免异步更新导致的状态误差 |
状态管理示例对比
jsx
// 类组件:状态管理
class ClassCounter extends React.Component {
// 状态初始化
state = { count: 0, name: "计数器" };
// 状态更新(自动合并顶层属性)
increment = () => {
this.setState({ count: this.state.count + 1 }); // 普通更新
// 函数式更新(依赖当前状态,更安全)
// this.setState(prevState => ({ count: prevState.count + 1 }));
};
render() {
return (
<div>
<p>{this.state.name}:{this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}
// 函数组件:状态管理
import { useState } from 'react';
const FunctionCounter = () => {
// 状态初始化(支持惰性初始化:useState(() => 0))
const [count, setCount] = useState(0);
const [name, setName] = useState("计数器");
const increment = () => {
setCount(count + 1); // 普通更新
// 函数式更新(推荐,依赖当前状态时更准确)
// setCount(prevCount => prevCount + 1);
};
// 对象类型状态:需手动合并(替换式更新)
const [user, setUser] = useState({ name: "张三", age: 18 });
const updateAge = () => {
setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }));
};
return (
<div>
<p>{name}:{count}</p>
<button onClick={increment}>+1</button>
</div>
);
};
三、 生命周期/副作用处理差异
类组件有明确的生命周期方法,函数组件通过 useEffect Hook 统一处理副作用,实现生命周期的功能:
| 特性 | 类组件 | 函数组件(Hooks) |
|---|---|---|
| 处理方式 | 提供 固定的生命周期方法(分阶段执行),职责明确 | 无专门生命周期方法,通过 useEffect Hook 统一处理所有副作用(网络请求、定时器、DOM 操作等) |
| 核心生命周期映射 | 1. 挂载:componentDidMount;2. 更新:componentDidUpdate;3. 卸载:componentWillUnmount | useEffect 通过「依赖数组」实现映射:1. 挂载(空数组 []);2. 更新(指定依赖);3. 卸载(返回清理函数) |
| 逻辑聚合性 | 相关逻辑可能分散在多个生命周期方法中(如网络请求在 componentDidMount,更新请求在 componentDidUpdate) | 相关逻辑聚合在一个 useEffect 中,代码内聚性更强,更易维护 |
生命周期/副作用示例对比
jsx
// 类组件:生命周期处理
class ClassLifecycle extends React.Component {
state = { data: [] };
// 挂载阶段:发起网络请求、初始化定时器
componentDidMount() {
this.fetchData();
this.timer = setInterval(() => console.log("定时器执行"), 1000);
}
// 更新阶段:props 变化时重新请求数据
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.fetchData();
}
}
// 卸载阶段:清理副作用(清除定时器、取消请求)
componentWillUnmount() {
clearInterval(this.timer);
}
fetchData = () => {
fetch(`https://api.example.com/data/${this.props.id}`)
.then(res => res.json())
.then(data => this.setState({ data }));
};
render() {
return <div>{this.state.data.map(item => item.name)}</div>;
}
}
// 函数组件:useEffect 模拟生命周期
import { useState, useEffect } from 'react';
const FunctionLifecycle = (props) => {
const [data, setData] = useState([]);
// 聚合:挂载 + 更新 + 卸载逻辑
useEffect(() => {
// 模拟 componentDidMount + componentDidUpdate
const fetchData = async () => {
const res = await fetch(`https://api.example.com/data/${props.id}`);
const result = await res.json();
setData(result);
};
fetchData();
// 模拟 componentWillUnmount(清理函数)
const timer = setInterval(() => console.log("定时器执行"), 1000);
return () => {
clearInterval(timer);
};
}, [props.id]); // 依赖 props.id,仅当 id 变化时执行
return <div>{data.map(item => item.name)}</div>;
};
四、 事件处理差异
两者的事件处理核心差异在于 this 指向问题,这也是类组件的常见痛点:
| 特性 | 类组件 | 函数组件(Hooks) |
|---|---|---|
this 指向问题 | 普通类方法的 this 默认指向 undefined(严格模式下),存在「this 丢失」问题 | 无 this 概念,事件处理函数无需绑定 this,直接使用即可 |
this 绑定解决方案 | 三种方式:1. 箭头函数(类实例属性);2. 构造函数中 bind;3. 渲染时内联箭头函数 | 无需绑定,直接定义箭头函数或普通函数即可 |
| 代码简洁性 | 需额外处理 this 绑定,代码冗余 | 无绑定开销,代码更简洁直观 |
事件处理示例对比
jsx
// 类组件:事件处理(需处理 this 绑定)
class ClassEvent extends React.Component {
// 方案1:箭头函数(推荐,无需手动绑定)
handleClick = () => {
console.log("点击事件", this.props.name); // this 指向组件实例
};
// 方案2:构造函数 bind 绑定
// constructor(props) {
// super(props);
// this.handleClick = this.handleClick.bind(this);
// }
// handleClick() {
// console.log("点击事件", this.props.name);
// }
render() {
return <button onClick={this.handleClick}>类组件按钮</button>;
}
}
// 函数组件:事件处理(无需 this 绑定)
const FunctionEvent = (props) => {
// 直接定义函数,无需处理 this
const handleClick = () => {
console.log("点击事件", props.name);
};
return <button onClick={handleClick}>函数组件按钮</button>;
};
五、 性能优化差异
两者的性能优化思路一致(避免不必要的重渲染、减少重复计算),但使用的 API 不同:
| 特性 | 类组件 | 函数组件(Hooks) |
|---|---|---|
| 组件级缓存 | 基于 React.PureComponent(自动浅比较 props/state)或 shouldComponentUpdate(手动控制更新) | 基于 React.memo(高阶组件,浅比较 props,对应 PureComponent) |
| 计算结果缓存 | 无原生 API,需手动缓存(如将计算结果存入 state) | 基于 useMemo Hook,缓存复杂计算结果,避免每次渲染重复计算 |
| 函数引用缓存 | 无原生 API,需手动处理(如将函数绑定为实例属性) | 基于 useCallback Hook,缓存函数引用,避免因函数引用变化导致子组件不必要渲染 |
| 优化核心逻辑 | 控制组件是否触发更新(通过 shouldComponentUpdate/PureComponent) | 1. 组件级:React.memo;2. 计算级:useMemo;3. 函数级:useCallback |
性能优化示例对比
jsx
// 类组件:性能优化
class ClassOptimize extends React.PureComponent {
// 若不使用 PureComponent,可手动编写 shouldComponentUpdate
// shouldComponentUpdate(nextProps, nextState) {
// return nextProps.name !== this.props.name; // 仅 name 变化时更新
// }
// 复杂计算:需手动缓存,否则每次 render 重复执行
getExpensiveValue = () => {
return this.props.list.filter(item => item > 0).sort((a, b) => a - b);
};
render() {
const sortedList = this.getExpensiveValue();
return <div>{sortedList.join(',')}</div>;
}
}
// 函数组件:性能优化
import { useState, memo, useMemo, useCallback } from 'react';
// 组件级缓存:React.memo
const FunctionOptimize = memo((props) => {
// 计算结果缓存:useMemo,仅 list 变化时重新计算
const sortedList = useMemo(() => {
return props.list.filter(item => item > 0).sort((a, b) => a - b);
}, [props.list]);
// 函数引用缓存:useCallback,仅依赖变化时更新函数引用
const handleClick = useCallback(() => {
console.log("点击", props.name);
}, [props.name]);
return (
<div>
<div>{sortedList.join(',')}</div>
<button onClick={handleClick}>按钮</button>
</div>
);
});
六、 逻辑复用差异
逻辑复用是组件设计的重要需求,两者的实现方式差异极大,这也是函数组件成为主流的核心原因之一:
| 特性 | 类组件 | 函数组件(Hooks) |
|---|---|---|
| 复用方式 | 1. 高阶组件(HOC);2. Render Props(渲染属性) | 基于 自定义 Hooks(独立函数,复用逻辑) |
| 复用痛点 | 1. 高阶组件:产生「嵌套地狱」(组件层级嵌套过深,难以调试);2. Render Props:代码嵌套复杂,可读性差 | 无嵌套问题,自定义 Hooks 是扁平的函数调用,代码简洁、可读性强 |
| 逻辑内聚性 | 复用逻辑与组件自身逻辑分离,难以维护 | 复用逻辑聚合在自定义 Hooks 中,与组件逻辑无缝集成 |
| 学习成本 | 较高(需理解高阶组件、Render Props 的设计思想) | 较低(自定义 Hooks 是普通函数,符合直觉) |
逻辑复用示例对比
jsx
// 类组件:高阶组件实现逻辑复用(定时器逻辑)
function withTimer(WrappedComponent) {
return class extends React.Component {
state = { time: 0 };
timer = null;
componentDidMount() {
this.timer = setInterval(() => {
this.setState(prevState => ({ time: prevState.time + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
return <WrappedComponent {...this.props} time={this.state.time} />;
}
};
}
// 使用高阶组件
class ClassTimer extends React.Component {
render() {
return <div>计时:{this.props.time} 秒</div>;
}
}
const ClassTimerWithHOC = withTimer(ClassTimer);
// 函数组件:自定义 Hooks 实现逻辑复用(定时器逻辑)
import { useState, useEffect } from 'react';
// 自定义 Hook:复用定时器逻辑
function useTimer(initialTime = 0) {
const [time, setTime] = useState(initialTime);
useEffect(() => {
const timer = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return time;
}
// 使用自定义 Hook
const FunctionTimer = () => {
const time = useTimer(0); // 直接调用 Hook,复用逻辑
return <div>计时:{time} 秒</div>;
};
七、 核心差异总结表
| 对比维度 | 类组件 | 函数组件(Hooks) |
|---|---|---|
| 定义形式 | ES6 Class,继承 React.Component | 普通函数/箭头函数,无继承 |
| 组件实例 | 有实例,存在 this 指向 | 无实例,无 this 概念 |
| 状态管理 | this.state + setState(对象合并更新) | useState/useReducer(替换式更新) |
| 生命周期/副作用 | 固定生命周期方法(分散逻辑) | useEffect(聚合逻辑,依赖数组控制执行) |
| 事件处理 | 需处理 this 绑定问题 | 无需 this 绑定,代码简洁 |
| 性能优化 | PureComponent/shouldComponentUpdate | React.memo/useMemo/useCallback |
| 逻辑复用 | 高阶组件/Render Props(嵌套问题) | 自定义 Hooks(扁平复用,易维护) |
| 代码简洁性 | 模板化冗余,代码量较大 | 轻量简洁,逻辑内聚性强 |
| 学习成本 | 较高(this + 生命周期 + 复用模式) | 较低(Hooks 语义清晰,符合直觉) |
| 未来趋势 | 逐渐被淘汰,仅用于老项目维护 | React 官方推荐,主流方案,支持最新特性 |
八、 适用场景总结
-
类组件适用场景:
- 维护老旧 React 项目(项目中大量使用类组件,无需重构为函数组件)
- 团队成员对 Hooks 不熟悉,暂时无法迁移
- 特殊场景(如需要使用
getSnapshotBeforeUpdate等小众生命周期方法,函数组件实现复杂)
-
函数组件适用场景:
- 新项目开发(React 官方推荐,后续会持续优化 Hooks 生态)
- 需复用组件逻辑(自定义 Hooks 比高阶组件更优雅)
- 追求代码简洁性和可维护性(无
this困扰,逻辑聚合) - 需使用 React 最新特性(如 Server Components、Concurrent Mode 等,优先支持函数组件)
总结
- 类组件和函数组件的核心差异源于定义形式和是否存在组件实例,这决定了两者在状态管理、事件处理等方面的语法差异。
- 函数组件通过 Hooks 实现了类组件的所有能力,且在代码简洁性、逻辑复用、性能优化灵活性上更具优势,是当前 React 开发的主流。
- 类组件并未完全过时,但仅适用于老项目维护,新项目应优先使用函数组件。
- 两者的核心功能一致(渲染视图、管理状态、处理副作用),差异主要在于实现方式和开发体验。