以下是React生命周期的详细解析,包含具体应用场景、变更原因及图示说明,基于React 17/18版本(截至2025年6月)。
一、React生命周期全阶段图解与说明
新版生命周期流程图(React 16.4+)
1 | graph TD |
二、各阶段详解与应用场景
1. 挂载阶段(Mounting)
constructor()- 场景:初始化state(如表单默认值)、绑定事件方法
- 示例:
1
2
3
4
5constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
static getDerivedStateFromProps(props, state)- 场景:props变化时同步state(如分页组件接收外部页码参数)
- 限制:必须是纯函数,无副作用
- 示例:
1
2
3static getDerivedStateFromProps(props) {
return { page: props.initialPage }; // 用props初始化state
}
render()- 规则:返回JSX,禁止执行
setState或操作DOM
- 规则:返回JSX,禁止执行
componentDidMount()- 核心场景:
- 数据请求(AJAX/Fetch)
- DOM操作(集成D3.js图表)
- 订阅事件(WebSocket监听)
- 示例:
1
2
3
4componentDidMount() {
fetch('/api/data').then(res => this.setState({ data: res.data }));
this.timer = setInterval(() => this.tick(), 1000);
}
- 核心场景:
2. 更新阶段(Updating)
shouldComponentUpdate(nextProps, nextState)- 场景:性能优化(阻止不必要的渲染)
- 示例:
1
2
3shouldComponentUpdate(nextProps) {
return nextProps.userID !== this.props.userID; // 仅当userID变化时重渲染
}
getSnapshotBeforeUpdate(prevProps, prevState)- 场景:DOM更新前捕获状态(如滚动位置保持)
- 经典案例:聊天窗口新消息插入时保持视图位置
1
2
3
4
5
6
7getSnapshotBeforeUpdate() {
return this.listRef.scrollHeight; // 返回更新前容器高度
}
componentDidUpdate(prevProps, prevState, snapshot) {
// snapshot为更新前高度,计算差值调整滚动条
this.listRef.scrollTop += this.listRef.scrollHeight - snapshot;
}
componentDidUpdate(prevProps)- 场景:
- props变化后重新请求数据(如路由参数变更)
- 更新第三方库(如图表数据更新)
1
2
3
4
5componentDidUpdate(prevProps) {
if (this.props.productID !== prevProps.productID) {
this.fetchProductData(); // ID变化时重新拉取数据
}
}
- 场景:
3. 卸载与错误处理
componentWillUnmount()- 场景:清理定时器/事件监听/取消请求
1
2
3
4componentWillUnmount() {
clearInterval(this.timer);
window.removeEventListener('resize', this.handleResize);
}
- 场景:清理定时器/事件监听/取消请求
错误边界(Error Boundaries)
- **
static getDerivedStateFromError()**:渲染备用UI - **
componentDidCatch()**:记录错误日志 - 示例:
1
2
3
4
5
6
7
8
9class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true }; // 触发降级UI
}
componentDidCatch(error, info) {
logErrorToService(error, info); // 上报错误
}
}
- **
三、生命周期变更的核心原因
1. Fiber架构的异步渲染需求
React 16引入Fiber架构以实现可中断渲染,旧生命周期(如componentWillMount)可能被多次调用,导致:
- 资源泄露:重复创建订阅/请求
- 状态不一致:中断后恢复时props/state不同步
2. 副作用规范化
- 问题:
componentWillMount中的异步操作可能操作未挂载的DOM - 解决:将副作用(数据请求等)移至
componentDidMount,保证DOM就绪
3. 不安全方法的滥用
componentWillReceiveProps反模式:新版通过**1
2
3
4// 旧版:易错误同步props到state
componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value }); // 破坏单一数据源
}getDerivedStateFromProps**强制纯函数解决
四、Hooks对生命周期的替代方案
函数组件+Hooks成为主流范式(React 16.8+):
| 类组件生命周期 | Hooks等效方案 | 示例 |
|---|---|---|
componentDidMount |
useEffect(..., []) |
初始化请求数据 |
componentDidUpdate |
useEffect(..., [deps]) |
依赖项更新后执行逻辑 |
componentWillUnmount |
useEffect返回清理函数 |
清除定时器/订阅 |
shouldComponentUpdate |
React.memo + useMemo |
浅比较props避免重渲染 |
getDerivedStateFromProps |
useState + useEffect |
根据props更新state |
✅ Hooks优势:逻辑复用(自定义Hook)、无
this绑定问题、代码更简洁
五、关键场景对比表
| 场景 | 类组件方案 | 函数组件方案 |
|---|---|---|
| 数据请求 | componentDidMount |
useEffect + 空依赖数组 |
| 滚动位置保持 | getSnapshotBeforeUpdate |
useLayoutEffect + ref保存位置 |
| 事件订阅/清理 | componentDidMount + componentWillUnmount |
useEffect返回清理函数 |
| props派生state | getDerivedStateFromProps |
useState + useEffect监视props |
| 错误捕获 | getDerivedStateFromError |
暂无直接等效(需类组件错误边界) |
六、官方变更总结与最佳实践
废弃原因
- 🚫
componentWillMount:服务端渲染可能重复调用 - 🚫
componentWillReceiveProps:易错误同步props/state - 🚫
componentWillUpdate:操作DOM导致状态不一致
- 🚫
迁移策略
- 新项目:函数组件 + Hooks
- 旧项目改造:
- 用
getDerivedStateFromProps替代componentWillReceiveProps - 用
getSnapshotBeforeUpdate替代componentWillUpdate
- 用
设计思想演进
- 同步 → 异步:为Concurrent Mode提供中断/优先级调度能力
- 副作用隔离:渲染阶段保持纯净,副作用集中到Commit阶段
- 函数式优先:Hooks推动函数式编程,简化组件逻辑
附:生命周期完整对比图(新旧版本)
(来源:React官方社区图表)