函数组件与类组件
聊聊这两兄弟到底有什么区别
先说结论:在 Hooks 出来之前,函数组件就是个"打酱油"的,只能展示点静态内容。类组件才是"全能选手"。但自从 React 16.8 推出 Hooks 之后,函数组件原地起飞,直接逆袭成了主流。
这就是 React 组件开发方式的演进史。
1. 写法对比
类组件
class ClassComponent extends React.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>
);
}
}
特点:
- 是个 ES6 Class,得
extends React.Component
- 必须写个
render() 方法返回 JSX
- 用
this.state 存状态,this.setState() 改状态
- 各种方法里都要带个
this,贼麻烦
函数组件
function FunctionComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>增加</button>
</div>
);
}
特点:
- 就是一个普普通通的函数
- 用
useState 存状态,setCount 改状态
- 没有
this,清爽多了
2. 能力对比表
| 能力 | 类组件 | 函数组件(现在) |
|---|
| 状态管理 | this.state + this.setState() | useState, useReducer |
| 生命周期 | componentDidMount 一大堆 | 一个 useEffect 全搞定 |
| 上下文 | MyContext.Consumer | useContext |
| 副作用 | 分散在各个生命周期里 | 放在同一个 useEffect 里 |
| 性能优化 | shouldComponentUpdate | React.memo, useMemo |
| Ref | React.createRef() | useRef |
| 逻辑复用 | HOC、Render Props 那一套 | 自定义 Hook,简单粗暴 |
3. 心智模型完全不同
这才是理解两者区别的核心。
类组件:面向对象那套
类组件就像创建一个对象实例。这个实例在组件的整个生命周期里都存在,状态就挂在实例上面。
class Counter extends React.Component {
this.state = { count: 0 }; // 状态长在实例身上
}
痛点来了:
-
this 绑定烦死人
// 必须这么写,不然 this 就丢了
<button onClick={this.increment.bind(this)}>
// 或者在构造函数里写
this.increment = this.increment.bind(this);
-
逻辑被拆得稀碎
- 订阅逻辑:
componentDidMount 里写订阅
- 取消订阅:
componentWillUnmount 里写取消
- 本来是一套完整的逻辑,非得拆到两个生命周期里
-
组件大了没法看
- 一个类组件可能有 300 行
- 状态、逻辑、方法全挤在一起
- 改起来胆战心惊,生怕改坏啥
函数组件:函数式编程
函数组件每次渲染都是一个独立的函数调用。这次调用完了,函数里的变量就全销毁了,下次渲染又是全新的调用。
function Counter() {
const [count, setCount] = useState(0); // 每次渲染都是新调用
return <div>{count}</div>;
}
那状态咋保持?靠 Hooks。Hooks 帮 React 在多次渲染之间"记住"了状态。
好处就很明显了:
- 不用写
class,代码更短
- 不用纠结
this 是谁
- 逻辑可以按功能分开,而不是按生命周期分开
- 自定义 Hook 是真的香,状态逻辑可以抽出来到处复用
4. 常见坑
函数组件的闭包陷阱
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
// 大坑!这里的 count 永远是 0
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []); // 空依赖,只跑一次
return <div>{count}</div>;
}
解释一下:effect 只在第一次渲染时执行一次,那时候 count 是 0。定时器每次触发都用的那个 0,永远不会变。
两种解法:
// 写法1:用函数式更新
setCount((c) => c + 1);
// 写法2:把 count 放进依赖数组
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]); // count 变了就重新建定时器
类组件的 this 问题
忘了绑定 this,点击按钮直接报错——这几乎是每个 React 开发者的必经之路。
5. 到底该用哪个?
毫无疑问,新项目就用函数组件。
React 官方现在全是往函数组件方向搞的,新特性也都优先给函数组件。类组件基本属于"维护模式"了。
什么时候还得用类组件?
- 维护老项目,里面一大把类组件
- 某些组件特别稳定,改了可能引入 bug,那就别动
- 极少数特殊情况:
getSnapshotBeforeUpdate 和 componentDidCatch 目前在函数组件里没有完全等价的 Hook
面试咋说?
记住这几点:
-
历史视角:Hooks 是分水岭,之前函数组件是残血的,之后才完整了
-
核心区别:类组件是"持续存在的实例",函数组件是"每次独立的调用"——这句话说清楚,心智模型就对了
-
能力对应:能说出类组件的特性对应哪些 Hook
-
优势在哪:代码简洁、逻辑复用不用(自定义 Hook)管 this
-
提到坑:闭包陷阱及其解法,说明真的写过
-
态度明确:新开发就用函数组件,类组件只用于维护旧代码