React 组件生命周期:从底层原理到实践场景
-
- [React 组件生命周期:从底层原理到实践场景](#React 组件生命周期:从底层原理到实践场景)
- [**一、生命周期的底层架构:Fiber 与调和过程**](#一、生命周期的底层架构:Fiber 与调和过程)
- [**二、Class 组件生命周期:三阶段详解**](#二、Class 组件生命周期:三阶段详解)
-
- [**阶段 1:挂载(Mounting)------ 组件首次渲染到 DOM**](#阶段 1:挂载(Mounting)—— 组件首次渲染到 DOM)
- [**阶段 2:更新(Updating)------ 组件因 props/state 变化重新渲染**](#阶段 2:更新(Updating)—— 组件因 props/state 变化重新渲染)
- [**阶段 3:卸载(Unmounting)------ 组件从 DOM 移除**](#阶段 3:卸载(Unmounting)—— 组件从 DOM 移除)
- [**阶段 4:错误处理(Error Handling)------ 组件渲染出错时**](#阶段 4:错误处理(Error Handling)—— 组件渲染出错时)
- [**三、Function 组件的生命周期:Hooks 模拟**](#三、Function 组件的生命周期:Hooks 模拟)
- **四、废弃的生命周期方法与原因**
- **五、最佳实践**
- **总结**
- **相关文章**
React 组件生命周期:从底层原理到实践场景
React 组件的生命周期是指组件从创建、更新到销毁 的过程中,React 自动调用的一系列方法(Class 组件)或钩子函数(Function 组件)。理解生命周期的核心是掌握 React 渲染流程(调度→协调→提交) 中各阶段的触发时机,以及如何在合适的时机执行副作用(如数据请求、DOM 操作、事件订阅)。
一、生命周期的底层架构:Fiber 与调和过程
React 16 引入 Fiber 架构后,生命周期的执行时机与 Fiber 树的构建和更新密切相关。Fiber 是可中断、可恢复的异步渲染单元,将渲染过程分为两个阶段:
-
协调阶段(Reconciliation Phase):
- 构建 Fiber 树(虚拟 DOM 的链表表示),标记需要更新的节点("Diff 算法")。
- 可中断 (通过
requestIdleCallback调度),期间执行纯计算逻辑 (如render方法)。
-
提交阶段(Commit Phase):
- 将 Fiber 树的变化同步提交到真实 DOM ,执行副作用(如
componentDidMount、DOM 操作)。 - 不可中断,确保 UI 一致性。
- 将 Fiber 树的变化同步提交到真实 DOM ,执行副作用(如
生命周期方法(或 Hooks)的执行时机,本质是 Fiber 树在不同阶段(挂载、更新、卸载)的回调。
二、Class 组件生命周期:三阶段详解
Class 组件的生命周期分为 挂载(Mounting) 、更新(Updating) 、卸载(Unmounting) 和 错误处理(Error Handling) 四大阶段。
阶段 1:挂载(Mounting)------ 组件首次渲染到 DOM
当组件实例被创建并插入 DOM 时触发,对应 Fiber 树的初次构建。
| 生命周期方法 | 执行时机 | 底层原理 | 使用场景 |
|---|---|---|---|
constructor(props) |
组件实例化时最先执行 ,在 render 前。 |
初始化 state、绑定事件处理函数(this),避免后续重复执行。 |
- 初始化 state(如 this.state = { count: 0 }) - 绑定 this(如 this.handleClick = this.handleClick.bind(this)) |
static getDerivedStateFromProps(props, state) |
render 前 执行(挂载和更新阶段均触发),静态方法 (无 this)。 |
根据 props 派生 state(替代废弃的 componentWillReceiveProps)。 |
- props 变化时同步更新 state(如表单初始值随 props 变化) - 注意:返回 { stateToUpdate } 或 null(不更新) |
render() |
协调阶段核心方法,返回 JSX 虚拟 DOM。 | Fiber 树构建的依据,纯函数(无副作用),需返回一致的 UI。 | - 渲染组件 UI(JSX) - 禁止在此执行副作用(如 API 请求、DOM 操作) |
componentDidMount() |
提交阶段执行,DOM 已渲染完成。 | 同步执行副作用(此时 DOM 可用),常用于一次性操作。 | - 发起数据请求(fetch/axios) - 订阅事件(如 WebSocket、Redux) - 初始化第三方库(如图表库) |
示例:挂载阶段数据请求
jsx
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = { user: null };
}
componentDidMount() {
// DOM 渲染后请求数据(避免阻塞渲染)
fetch(`/api/users/${this.props.userId}`)
.then(res => res.json())
.then(user => this.setState({ user }));
}
render() {
return this.state.user ? <div>{this.state.user.name}</div> : <div>Loading...</div>;
}
}
阶段 2:更新(Updating)------ 组件因 props/state 变化重新渲染
当组件的 props 或 state 变化时触发,对应 Fiber 树的增量更新。
| 生命周期方法 | 执行时机 | 底层原理 | 使用场景 |
|---|---|---|---|
static getDerivedStateFromProps(props, state) |
同挂载阶段,render 前执行。 |
同上,确保 props 变化时 state 同步更新。 |
同上 |
shouldComponentUpdate(nextProps, nextState) |
render 前 执行,返回 boolean。 |
Fiber 协调阶段的"优化开关":返回 false 跳过本次渲染(避免不必要 Diff)。 |
- 性能优化:对比 nextProps/nextState 与当前值,无变化时返回 false - 注意:默认返回 true(必须谨慎使用) |
render() |
同挂载阶段,返回更新后的虚拟 DOM。 | Fiber 树增量更新,标记变化的节点。 | 同上 |
getSnapshotBeforeUpdate(prevProps, prevState) |
render 后、DOM 更新前执行(提交阶段前置)。 |
捕获 DOM 更新前的状态(如滚动位置、元素尺寸),返回值传给 componentDidUpdate。 |
- 保存滚动位置(如聊天窗口) - 记录 DOM 元素尺寸(如折叠面板高度) |
componentDidUpdate(prevProps, prevState, snapshot) |
提交阶段执行,DOM 已更新完成。 | 执行更新后的副作用(如基于新 DOM 的操作)。 | - 数据请求(如搜索关键词变化后重新请求) - 更新第三方库配置(如图表重绘) - 使用 snapshot 恢复状态(如滚动位置) |
示例:更新阶段性能优化与滚动位置保存
jsx
class ChatWindow extends React.Component {
constructor(props) {
super(props);
this.messagesEndRef = React.createRef();
}
shouldComponentUpdate(nextProps) {
// 仅当消息列表变化时更新(避免头像等无关变化触发渲染)
return nextProps.messages !== this.props.messages;
}
getSnapshotBeforeUpdate(prevProps) {
// 保存更新前的滚动位置(若消息新增)
if (prevProps.messages.length < this.props.messages.length) {
return this.messagesEndRef.current.scrollHeight;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 恢复滚动位置(新增消息后自动滚动到底部)
if (snapshot !== null) {
this.messagesEndRef.current.scrollTop = snapshot - this.messagesEndRef.current.clientHeight;
}
}
render() {
return (
<div ref={this.messagesEndRef}>
{this.props.messages.map(msg => <div key={msg.id}>{msg.text}</div>)}
</div>
);
}
}
阶段 3:卸载(Unmounting)------ 组件从 DOM 移除
当组件实例被销毁时触发,对应 Fiber 树的节点删除。
| 生命周期方法 | 执行时机 | 底层原理 | 使用场景 |
|---|---|---|---|
componentWillUnmount() |
提交阶段执行,DOM 即将移除前。 | 清理副作用(避免内存泄漏)。 | - 取消 API 请求(如 AbortController) - 移除事件监听(removeEventListener) - 清除定时器(clearInterval) - 取消订阅(如 Redux unsubscribe) |
示例:卸载阶段清理副作用
jsx
class TimerComponent extends React.Component {
intervalId = null;
componentDidMount() {
this.intervalId = setInterval(() => {
console.log('Timer tick');
}, 1000);
}
componentWillUnmount() {
// 清除定时器,避免组件卸载后仍执行
clearInterval(this.intervalId);
}
render() {
return <div>Timer running...</div>;
}
}
阶段 4:错误处理(Error Handling)------ 组件渲染出错时
当组件渲染、生命周期或子组件构造函数抛出错误时触发。
| 生命周期方法 | 执行时机 | 底层原理 | 使用场景 |
|---|---|---|---|
static getDerivedStateFromError(error) |
错误发生后渲染备用 UI 前执行(静态方法)。 | 根据错误更新 state,返回 { hasError: true } 触发备用 UI 渲染。 |
- 捕获子组件错误,显示降级 UI(如"加载失败,请重试") |
componentDidCatch(error, info) |
提交阶段执行,错误已发生,可记录错误信息。 | 执行错误上报(如 Sentry)、日志记录。 | - 上报错误到监控系统 - 显示错误详情(仅开发环境) |
示例:错误边界处理子组件错误
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true }; // 更新 state 触发备用 UI
}
componentDidCatch(error, info) {
// 上报错误到监控系统
logErrorToService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>; // 备用 UI
}
return this.props.children;
}
}
// 使用:包裹可能出错的组件
<ErrorBoundary>
<UserProfile userId="123" />
</ErrorBoundary>
三、Function 组件的生命周期:Hooks 模拟
Function 组件通过 Hooks 模拟 Class 组件的生命周期,核心是用 useState、useEffect、useLayoutEffect 等钩子函数对应不同阶段。
| Class 生命周期 | Function 组件 Hooks 实现 | 执行时机 |
|---|---|---|
constructor + state |
useState(initialState) |
组件首次渲染初始化 state。 |
getDerivedStateFromProps |
useState + useEffect(监听 props 变化)或自定义 Hook(如 usePropsState) |
props 变化时更新 state(类似 Class 的派生状态)。 |
shouldComponentUpdate |
React.memo(Component, areEqual)(浅比较 props)或 useMemo(缓存计算结果) |
阻止不必要的渲染(性能优化)。 |
render |
函数组件本体(返回 JSX) | 协调阶段执行,返回虚拟 DOM。 |
componentDidMount |
useEffect(() => {}, [])(依赖数组为空) |
提交阶段执行,DOM 渲染后(异步执行)。 |
componentDidUpdate |
useEffect(() => {}, [deps])(依赖数组含变量) |
依赖变化时执行(DOM 更新后,异步执行)。 |
getSnapshotBeforeUpdate |
useLayoutEffect(同步执行,在 DOM 更新后、绘制前) |
捕获 DOM 更新前的状态(同步执行,适合读取 DOM 布局)。 |
componentWillUnmount |
useEffect 返回的清理函数(return () => {}) |
组件卸载前执行(清理副作用)。 |
componentDidCatch |
react-error-boundary 库(Function 组件需用第三方库实现错误边界) |
捕获子组件错误并执行副作用。 |
示例:Function 组件模拟生命周期
jsx
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
function TimerComponent() {
const [count, setCount] = useState(0);
const intervalIdRef = useRef(null);
// 模拟 componentDidMount:初始化定时器
useEffect(() => {
intervalIdRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// 模拟 componentWillUnmount:清理定时器
return () => clearInterval(intervalIdRef.current);
}, []); // 依赖为空:仅执行一次(挂载时)
// 模拟 componentDidUpdate:count 变化时打印日志
useEffect(() => {
console.log(`Count updated: ${count}`);
}, [count]); // 依赖为 count:count 变化时执行
// 模拟 getSnapshotBeforeUpdate + componentDidUpdate:保存滚动位置
const scrollRef = useRef(null);
useLayoutEffect(() => {
if (scrollRef.current) {
const prevScrollHeight = scrollRef.current.dataset.prevScrollHeight;
if (prevScrollHeight) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight - prevScrollHeight;
}
// 保存当前滚动高度供下次更新使用
scrollRef.current.dataset.prevScrollHeight = scrollRef.current.scrollHeight;
}
});
return (
<div ref={scrollRef}>
<p>Count: {count}</p>
</div>
);
}
四、废弃的生命周期方法与原因
React 16.3+ 废弃了 3 个不安全的方法(与 Fiber 异步渲染冲突):
| 废弃方法 | 替代方案 | 废弃原因 |
|---|---|---|
componentWillMount |
constructor + componentDidMount |
Fiber 异步渲染可能导致多次执行,引发副作用(如数据请求)重复触发。 |
componentWillReceiveProps |
static getDerivedStateFromProps |
允许在渲染前更新 state,但易引发副作用(如直接修改 state),静态方法更安全。 |
componentWillUpdate |
getSnapshotBeforeUpdate + componentDidUpdate |
可能在渲染中被中断,导致 DOM 操作不安全(如读取过时 DOM 状态)。 |
五、最佳实践
- 优先使用 Function 组件 + Hooks :更简洁、无
this绑定问题,生命周期逻辑可通过自定义 Hook 复用(如useFetch封装数据请求)。 - 副作用放对位置 :
- DOM 操作、数据请求放
componentDidMount/useEffect(空依赖); - 清理副作用放
componentWillUnmount/useEffect返回函数。
- DOM 操作、数据请求放
- 性能优化 :
- 用
React.memo避免无关props变化触发渲染; - 用
useMemo/useCallback缓存计算结果和函数; - 谨慎使用
shouldComponentUpdate(优先让 React 自动优化)。
- 用
- 错误处理 :用错误边界(
ErrorBoundary组件)捕获子组件错误,避免整个应用崩溃。
总结
React 生命周期的本质是 Fiber 渲染流程在各阶段的回调 ,Class 组件通过固定方法暴露这些回调,Function 组件通过 Hooks 灵活模拟。掌握生命周期的核心是:明确副作用的执行时机(挂载后、更新后、卸载前),并结合底层 Fiber 架构理解"为何废弃某些方法"(异步渲染的安全性)。实际开发中,应根据场景选择合适的方式(Class/Hooks),优先用 Hooks 简化逻辑,同时注意性能优化与错误处理。