React 组件生命周期(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
- 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在 React 开发中,组件的生命周期管理是构建高效、可维护应用的核心技能之一。无论是处理数据初始化、更新状态,还是执行清理操作,开发者都需要对组件的“生命阶段”有清晰的理解。本文将通过通俗易懂的比喻、代码示例和实际场景,系统性地解析 React 组件的生命周期,帮助初学者和中级开发者掌握这一关键概念。
一、组件生命周期的定义与核心价值
1.1 什么是组件生命周期?
可以将 React 组件比作一个“有生命”的对象,它会经历创建、更新、销毁三个阶段。每个阶段对应一系列可被开发者定义的“钩子函数”(lifecycle methods),允许我们在特定时机执行自定义逻辑。例如:
- 出生阶段:组件被首次渲染到页面时,执行初始化操作(如数据请求);
- 成长阶段:当组件接收到新 props 或 state 变化时,执行更新逻辑;
- 死亡阶段:组件被移除时,清理资源(如取消定时器或订阅)。
1.2 生命周期的核心价值
- 控制渲染流程:确保在合适时机执行副作用(如网络请求、DOM 操作);
- 优化性能:避免不必要的渲染或资源泄漏;
- 统一逻辑管理:将业务代码与渲染逻辑分离,提升代码可读性。
二、类组件的生命周期详解(基于 React 16.x 以前版本)
尽管函数组件和 Hooks 已成为主流,但理解类组件的生命周期仍能帮助开发者更好地理解 React 的底层机制。
2.1 类组件的完整生命周期流程
以下是类组件从创建到销毁的典型流程:
| 阶段 | 方法名 | 作用说明 |
|--------------|---------------------------------|-----------------------------------|
| 初始化阶段 | constructor() | 构造函数,初始化 state 和绑定方法 |
| | static getDerivedStateFromProps() | 根据 props 更新 state(已废弃) |
| | componentDidMount() | 组件挂载后执行(如数据请求) |
| 更新阶段 | getDerivedStateFromProps() | (已废弃) |
| | shouldComponentUpdate() | 判断是否需要更新(性能优化) |
| | render() | 返回 JSX,决定 UI 结构 |
| | getSnapshotBeforeUpdate() | 获取更新前的 DOM 状态 |
| | componentDidUpdate() | 更新完成后执行(如滚动到顶部) |
| 销毁阶段 | componentWillUnmount() | 组件卸载前清理资源(如取消订阅) |
2.1.1 关键方法的深度解析
示例场景: 一个计数器组件,初始值为 0,每点击一次按钮递增 1。
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log("1. constructor()");
}
componentDidMount() {
console.log("4. componentDidMount()");
// 发起网络请求或订阅事件
}
shouldComponentUpdate(nextProps, nextState) {
console.log("5. shouldComponentUpdate()");
return nextState.count % 2 === 0; // 只允许偶数时更新
}
componentDidUpdate() {
console.log("7. componentDidUpdate()");
// 更新后的操作,如保存数据
}
componentWillUnmount() {
console.log("8. componentWillUnmount()");
// 清理定时器或取消订阅
}
render() {
console.log("6. render()");
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
执行顺序分析:
- constructor():初始化 state 和 props;
- render():首次渲染 UI;
- componentDidMount():挂载完成后执行,适合发起数据请求;
- shouldComponentUpdate():当 state/props 变化时,决定是否触发更新;
- render() 和 componentDidUpdate():更新后的逻辑执行。
2.2 生命周期的常见误区
- 不要在 render() 中直接修改 state:这会导致无限递归更新;
- componentWillMount 和 componentWillReceiveProps 已废弃:React 16.3 版本后,建议改用构造函数或 getSnapshotBeforeUpdate;
- 避免在生命周期中使用同步副作用:如直接操作 DOM,应改用 ref 或异步方法。
三、函数组件的生命周期(基于 Hooks 的现代化方案)
随着 React Hooks 的引入,函数组件通过 useEffect 等 Hook 实现了与类组件生命周期的兼容性,同时简化了代码结构。
3.1 useEffect 的核心作用
useEffect 可以替代以下类组件方法:
- componentDidMount:当依赖数组为空时(
[]
); - componentDidUpdate:当依赖变化时触发;
- componentWillUnmount:通过返回清理函数实现。
function Counter() {
const [count, setCount] = useState(0);
// 类似 componentDidMount + componentDidUpdate
useEffect(() => {
console.log("Component did mount/update");
// 发起请求或订阅
return () => {
console.log("Cleanup on unmount/update");
// 取消订阅或清除资源
};
}, [count]); // 依赖 count 变化时触发
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
3.2 其他常用 Hook 的生命周期映射
类组件方法 | 对应 Hook | 适用场景 |
---|---|---|
constructor() | useState, useEffect | 初始化 state 或资源 |
componentDidMount() | useEffect (依赖空数组) | 组件挂载后执行一次 |
componentDidUpdate() | useEffect (依赖变化) | 状态/props 变化后执行 |
componentWillUnmount() | useEffect 返回函数 | 组件销毁前清理资源 |
shouldComponentUpdate() | React.memo 或自定义逻辑 | 控制是否跳过渲染 |
四、实际案例:数据驱动的组件生命周期管理
4.1 场景:用户信息展示组件
需求:当组件首次加载时获取用户数据,当用户 ID 变化时重新请求,并在卸载时清除请求。
function UserInfo({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// 1. 组件挂载时执行
const fetchData = async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
};
fetchData();
// 2. 组件卸载时清理
return () => {
// 可在此处取消请求(如使用 AbortController)
};
// 3. 依赖 userId,当 userId 变化时重新执行
}, [userId]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
4.2 典型问题与解决方案
问题: 当用户快速切换 ID 时,如何避免之前的请求覆盖最新结果?
解决方案: 使用 AbortController
取消未完成的请求:
useEffect(() => {
let isMounted = true; // 标记组件是否已挂载
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(
`/api/users/${userId}`,
{ signal: controller.signal }
);
const data = await response.json();
if (isMounted) setUser(data);
} catch (error) {
if (error.name === "AbortError") return;
console.error("Fetch failed:", error);
}
};
fetchData();
return () => {
isMounted = false;
controller.abort();
};
}, [userId]);
五、常见问题与最佳实践
5.1 为什么我的组件没有触发 componentDidUpdate?
- 检查 shouldComponentUpdate 是否返回
false
,阻止了更新流程; - 确保 state 或 props 确实发生了变化。
5.2 如何避免内存泄漏?
- 在 useEffect 的清理函数中取消所有异步操作;
- 使用 useRef 存储非 state 数据,避免重复创建对象。
5.3 类组件与函数组件的选择建议
- 选择类组件:需要访问 ref 或兼容旧版库时;
- 选择函数组件 + Hooks:90% 的场景下更简洁易维护。
结论
掌握 React 组件生命周期是构建复杂应用的基石。无论是通过类组件的完整流程,还是通过 Hooks 的现代化方案,开发者都能通过生命周期方法实现精准的逻辑控制。本文通过代码示例和实际场景,帮助读者理解如何在初始化、更新、销毁阶段执行关键操作,最终写出高效、可维护的 React 代码。
在实际开发中,建议优先使用函数组件和 Hooks,同时结合性能分析工具(如 React Developer Tools)观察组件的渲染流程,逐步优化代码结构。记住,生命周期不是“魔法”,而是一套可预测的执行规则——理解它,就能更好地掌控你的 React 应用。