React 有两种组件写法:函数组件(Function Component) 和 类组件(Class Component)。现代 React 项目几乎全面使用函数组件,类组件属于历史遗留写法。
函数组件
函数组件就是一个普通的 JavaScript 函数,接收 Props,返回 JSX。
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// 也可以用箭头函数
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
React 16.8 引入 Hooks 之后,函数组件可以使用状态和生命周期,成为主流写法。
类组件
类组件通过继承 React.Component 实现,必须定义 render() 方法返回 JSX。
import { Component } from "react";
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
有状态的类组件:
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>计数:{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
+1
</button>
</div>
);
}
}
同一功能的对比
以一个带状态的计数器为例,对比两种写法的差异:
类组件写法:
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this); // 必须手动绑定 this
}
componentDidMount() {
document.title = `计数:${this.state.count}`;
}
componentDidUpdate() {
document.title = `计数:${this.state.count}`;
}
componentWillUnmount() {
document.title = "App";
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={this.handleClick}>
计数:{this.state.count}
</button>
);
}
}
函数组件写法:
import { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `计数:${count}`;
return () => { document.title = "App"; };
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
计数:{count}
</button>
);
}
两段代码功能完全相同,函数组件明显更简洁。
类组件的痛点
1. this 绑定问题
类组件中事件处理函数需要手动绑定 this,否则 this 会是 undefined。这是 JavaScript 中 this 机制导致的,容易出错且写法繁琐。
// 必须在 constructor 中绑定,否则出错
this.handleClick = this.handleClick.bind(this);
// 或者用箭头函数定义方法(class fields 语法)
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
函数组件完全不存在这个问题。
2. 生命周期方法把不相关逻辑混在一起
假设组件需要做两件不相关的事:订阅好友状态、开启定时器。
类组件中这两段逻辑会被强制拆散到不同生命周期方法里:
componentDidMount() {
// 逻辑 A:订阅
subscribeToFriend(this.props.friendId);
// 逻辑 B:定时器
this.timer = setInterval(this.tick, 1000);
}
componentWillUnmount() {
// 逻辑 A 的清理
unsubscribeFromFriend(this.props.friendId);
// 逻辑 B 的清理
clearInterval(this.timer);
}
函数组件中可以按逻辑归组,每段逻辑自成一个 useEffect,代码可读性更好:
// 逻辑 A 独立
useEffect(() => {
subscribeToFriend(friendId);
return () => unsubscribeFromFriend(friendId);
}, [friendId]);
// 逻辑 B 独立
useEffect(() => {
const timer = setInterval(tick, 1000);
return () => clearInterval(timer);
}, []);
3. 状态逻辑难以复用
类组件复用状态逻辑只能依赖 HOC(高阶组件)或 Render Props,这两种模式都会导致组件嵌套层级变深,调试困难。
函数组件可以把逻辑提取为自定义 Hook,直接在多个组件中调用,不改变组件结构。
// 提取为自定义 Hook,任意组件复用
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handler = () =>
setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);
return size;
}
为什么现在推荐函数组件
| 对比维度 | 函数组件 | 类组件 |
|---|---|---|
| 代码量 | 少,简洁 | 多,模板代码多 |
this 问题 |
无 | 需要手动绑定 |
| 逻辑组织 | 按关注点分组(useEffect) | 按生命周期强制分散 |
| 逻辑复用 | 自定义 Hook,灵活 | HOC / Render Props,复杂 |
| 性能优化 | React.memo + useMemo |
PureComponent / shouldComponentUpdate |
| React 未来方向 | ✅ 官方主推 | ⚠️ 不再新增特性 |
| React Compiler 支持 | ✅ 完整支持 | ❌ 不支持 |
React 官方在 16.8 之后明确表示:不会从类组件中移除任何功能,但新特性只会在函数组件中添加。React 19 的 React Compiler(自动 memoization 优化)也仅支持函数组件。
类组件还有用武之地吗
一个例外:错误边界(Error Boundary)。
目前错误边界只能用类组件实现,因为对应的生命周期 componentDidCatch 和 getDerivedStateFromError 尚无 Hook 等价物。
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error(error, info);
}
render() {
if (this.state.hasError) {
return <p>页面出错了</p>;
}
return this.props.children;
}
}
实际项目中通常直接使用 react-error-boundary 库,内部封装好了类组件,对外暴露友好的 API,无需自己写类组件。
总结
- 新项目:全面使用函数组件 + Hooks,不写类组件
- 旧项目维护:遇到类组件不必强制重写,但新增功能优先用函数组件
- 错误边界:使用
react-error-boundary库代替手写类组件