React 组件:函数组件与类组件对比
React 开发中,构建用户界面的基本单位是组件。目前最主流的组件定义方式有两种:函数组件和类组件。虽然随着 React 16.8 版本引入 Hooks 后,函数组件的功能已经非常强大,但在很多老旧项目或特定场景下,类组件依然存在。了解两者的区别与转换逻辑,是编写高质量 React 代码的基础。
1. 认识两种组件的定义方式
在代码层面,两者的写法有明显的结构差异。类组件是基于 ES6 Class 的语法糖,而函数组件本质就是一个 JavaScript 函数。
类组件
类组件必须继承自 React.Component,并且必须实现一个 render 方法,该方法返回 JSX。
import React, { Component } from 'react';
class CounterClass extends Component {
constructor(props) {
super(props);
// 初始化状态
this.state = {
count: 0
};
}
// 更新状态的方法
increment = () => {
this.setState({ count: this.state.count + 1 });
};
// 必须实现的渲染方法
render() {
return (
<div>
<p>当前计数: {this.state.count}</p>
<button onClick={this.increment}>增加</button>
</div>
);
}
}
export default CounterClass;
函数组件
函数组件没有 this,没有 render 方法,直接接收 props 并返回 JSX。
import React, { useState } from 'react';
function CounterFunction() {
// 使用 Hook 声明状态
const [count, setCount] = useState(0);
const increment = () => {
// 直接调用设置函数更新状态
setCount(count + 1);
};
return (
<div>
<p>当前计数: {count}</p>
<button onClick={increment}>增加</button>
</div>
);
}
export default CounterFunction;
2. 核心特性对比
通过以下表格,可以快速掌握两者在关键维度上的区别。
| 特性维度 | 类组件 | 函数组件 |
|---|---|---|
| 定义方式 | 使用 class 定义,继承 React.Component |
使用 function 定义,是一个纯函数 |
| 内部状态 | 使用 this.state 存储数据 |
使用 useState 或 useReducer Hook 存储数据 |
| 状态更新 | 必须调用 this.setState() |
调用 State Hook 返回的更新函数(如 setCount) |
| 生命周期 | 拥有完整的生命周期方法(如 componentDidMount) |
使用 useEffect Hook 统一处理副作用 |
| this 指向 | 需要处理 this 绑定问题(如 .bind(this) 或箭头函数) |
不存在 this,无需绑定 |
| 性能优化 | 使用 React.PureComponent 或 shouldComponentUpdate |
使用 React.memo、useMemo、useCallback |
| 代码量 | 较多,包含构造函数、render 方法等样板代码 | 较少,逻辑更加紧凑和直观 |
| 逻辑复用 | 主要依赖高阶组件(HOC)和 Render Props | 使用自定义 Hooks 极其便捷 |
3. 状态与生命周期的转换实操
将类组件重构为函数组件是现代 React 开发的常见需求。重点关注状态管理和生命周期方法的映射。
3.1 状态管理迁移
在类组件中,状态通常是一个对象,通过 this.setState 进行合并更新;在函数组件中,推荐将状态拆分为多个独立的变量。
迁移步骤:
- 移除
constructor和this.state。 - 引入
useStateHook。 - 将
this.state.count替换为count变量。 - 将
this.setState({ count: ... })替换为setCount(...)。
3.2 生命周期方法迁移
类组件的生命周期逻辑(如数据请求、订阅、手动修改 DOM)在函数组件中统一由 useEffect 接管。
迁移对照表:
| 类组件生命周期 | 函数组件 useEffect 依赖项 |
说明 |
|---|---|---|
componentDidMount |
useEffect(() => { ... }, []) |
空依赖数组表示仅在组件挂载后执行一次。 |
componentDidUpdate |
useEffect(() => { ... }, [someProp]) |
依赖数组包含变量,当变量变化时执行。 |
componentWillUnmount |
useEffect(() => { return () => { ... } }, []) |
useEffect 的返回函数会在组件卸载前执行。 |
代码示例:
假设一个组件需要在挂载时获取数据,并在卸载时清除计时器。
类组件写法:
class DataComponent extends Component {
state = { data: null };
timer = null;
componentDidMount() {
// 模拟数据获取
this.fetchData();
// 设置计时器
this.timer = setInterval(() => console.log('Tick'), 1000);
}
componentWillUnmount() {
// 清除计时器
clearInterval(this.timer);
}
fetchData = () => {
// fetch logic...
this.setState({ data: 'Loaded' });
};
render() {
return <div>{this.state.data}</div>;
}
}
函数组件转换写法:
import React, { useState, useEffect } from 'react';
function DataComponent() {
const [data, setData] = useState(null);
useEffect(() => {
// 对应 componentDidMount
const fetchData = () => {
// fetch logic...
setData('Loaded');
};
fetchData();
const timer = setInterval(() => console.log('Tick'), 1000);
// 对应 componentWillUnmount
return () => {
clearInterval(timer);
};
}, []); // 空数组确保只运行一次
return <div>{data}</div>;
}
4. 如何选择组件类型
在当前(2026年)的技术环境下,推荐策略如下:
优先选择函数组件:
- 新建项目:默认使用函数组件配合 Hooks。代码更少,逻辑复用性更强,且是 React 官方推荐的发展方向。
- 逻辑复用:如果需要在多个组件间共享状态逻辑,自定义 Hooks 比高阶组件更简洁直观。
- 性能优化:利用
useMemo和useCallback可以更细粒度地控制渲染,避免类组件中繁琐的this判断。
保留类组件的情况:
- 维护老项目:如果项目代码库中已经大量使用了类组件,且运行稳定,没有必要全部重写。
- 错误边界:目前错误边界组件必须使用类组件实现(因为依赖于
static getDerivedStateFromError或componentDidCatch生命周期)。 - 极其复杂的生命周期交互:虽然
useEffect很强大,但在极少数涉及特定渲染阶段精确控制的复杂场景下,类组件的生命周期可能更符合直觉。
5. 注意闭包陷阱的特性差异
在函数组件中,每一次渲染都会拥有独立的 props 和 state。函数内部捕获的值是渲染那一时刻的值。而类组件中的 this 始终指向最新的组件实例。
这意味着,如果在函数组件中使用了异步操作(如 setTimeout 或 setTimeout),需要注意回调函数中引用的 state 是否过期。
解决方法:
使用 useRef 保存可变变量,或者使用 setState 的函数式更新形式(setCount(prev => prev + 1)),确保总是获取到最新的状态。

暂无评论,快来抢沙发吧!