React 生命周期详解:从挂载到卸载
React 组件的生命周期(Lifecycle)是指组件从创建到销毁的整个过程,包括挂载、更新和卸载三个主要阶段。这套机制允许开发者在组件的不同生命周期节点注入自定义逻辑,如初始化状态、请求数据、清理资源等。
关键点:
- 主要针对类组件:React 16.3+ 引入 Hooks 后,函数组件通过
useEffect等 Hook 模拟生命周期。但本文重点讲解类组件的经典生命周期(基于 React 18+)。 - 已弃用方法:React 17+ 标记了
componentWillMount、componentWillReceiveProps、componentWillUpdate为 UNSAFE_(不推荐使用,可能在未来版本移除)。 - 异步渲染:React 16+ 支持异步渲染,生命周期方法调用顺序可能因 Fiber 架构而略有变化,但整体流程一致。
- Hooks 替代:函数组件更推荐,使用
useEffect、useState等实现类似功能。
下面按时间线(从挂载到卸载)详细拆解每个阶段,包括方法调用顺序、用途、代码示例和注意事项。
1. 挂载阶段(Mounting):组件首次渲染到 DOM
这一阶段从组件实例化开始,到插入 DOM 结束。顺序固定。
| 方法名称 | 调用时机 | 用途 / 常见操作 | 是否能访问 DOM | 异步渲染影响 |
|---|---|---|---|---|
| constructor() | 组件实例化时(props 传入前) | 初始化 state、绑定方法(this.xxx = this.xxx.bind(this)) | 否 | 无 |
| static getDerivedStateFromProps(props, state) | constructor 后 / render 前(静态方法) | 从 props 派生 state(很少用) | 否 | 无 |
| render() | 所有准备就绪后 | 返回 JSX / React 元素(纯函数,不能有副作用) | 否 | — |
| componentDidMount() | render 后,组件已插入 DOM | 请求数据、添加事件监听、setState(初始化后更新) | 是 | 无 |
代码示例:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.handleClick = this.handleClick.bind(this); // 绑定方法 } static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.initialCount !== prevState.count) { return { count: nextProps.initialCount }; // 从 props 更新 state } return null; } componentDidMount() { // 发起 API 请求 fetch('/api/data').then(res => this.setState({ data: res })); document.addEventListener('click', this.handleClick); // 添加全局监听 } render() { return <div>Count: {this.state.count}</div>; } }注意:
- 避免在 constructor 中调用 setState(props 还没初始化)。
- componentDidMount 是请求数据的黄金时机(不会阻塞渲染)。
- 已弃用:UNSAFE_componentWillMount(用 constructor 或 getDerivedStateFromProps 代替)。
2. 更新阶段(Updating):组件 props/state 变化时
当 props/state 变化、父组件重渲染或 forceUpdate() 时触发。顺序类似挂载,但多了一些更新前检查。
| 方法名称 | 调用时机 | 用途 / 常见操作 | 是否能访问 DOM | 异步渲染影响 |
|---|---|---|---|---|
| static getDerivedStateFromProps(props, state) | props/state 变化后,render 前 | 从新 props 更新 state | 否 | 无 |
| shouldComponentUpdate(nextProps, nextState) | getDerivedStateFromProps 后 | 性能优化:返回 false 阻止渲染(PureComponent 默认实现) | 否 | 无 |
| render() | shouldComponentUpdate 返回 true 后 | 返回新 JSX | 否 | — |
| getSnapshotBeforeUpdate(prevProps, prevState) | render 后,DOM 更新前 | 捕获 DOM 状态(如滚动位置)返回给 componentDidUpdate | 是 | 无 |
| componentDidUpdate(prevProps, prevState, snapshot) | DOM 更新后 | 更新后操作(如动画、请求新数据) | 是 | 无 |
代码示例:
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return nextProps.value !== this.props.value; // 浅比较,避免不必要渲染 } getSnapshotBeforeUpdate(prevProps, prevState) { // 捕获滚动位置 return document.getElementById('scroll-container').scrollTop; } componentDidUpdate(prevProps, prevState, snapshot) { if (this.props.value !== prevProps.value) { // 根据 snapshot 恢复滚动位置 document.getElementById('scroll-container').scrollTop = snapshot; // 或发起新请求 fetch(`/api/update?newValue=${this.props.value}`); } } render() { return <div>Value: {this.props.value}</div>; } }注意:
- shouldComponentUpdate 是性能调优的关键(但不要过度使用)。
- 已弃用:UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate(用 getDerivedStateFromProps / getSnapshotBeforeUpdate 代替)。
- componentDidUpdate 避免直接 setState(防止无限循环),用条件判断。
3. 卸载阶段(Unmounting):组件从 DOM 移除
当组件被移除(父组件不渲染它)时触发。只有一个方法。
| 方法名称 | 调用时机 | 用途 / 常见操作 | 是否能访问 DOM | 异步渲染影响 |
|---|---|---|---|---|
| componentWillUnmount() | 组件即将从 DOM 移除前 | 清理资源:移除事件监听、取消定时器、关闭连接 | 是 | 无 |
代码示例:
class MyComponent extends React.Component { componentDidMount() { this.timer = setInterval(() => console.log('tick'), 1000); document.addEventListener('click', this.handleClick); } componentWillUnmount() { clearInterval(this.timer); // 清理定时器 document.removeEventListener('click', this.handleClick); // 移除监听 // 关闭 WebSocket 等 } render() { return <div>Unmounting Example</div>; } }注意:
- 这里是清理资源的唯一时机(防止内存泄漏)。
- 不要在这里 setState(组件已卸载)。
- 已弃用:无(但老版本有 componentWillUnmount 的 UNSAFE_ 变体)。
4. 函数组件的生命周期(Hooks 模拟,2025 主流写法)
类组件生命周期正逐渐被 Hooks 取代。Hooks 更简洁、复用性强。
| 类组件方法 | Hooks 等价物 | 备注 |
|---|---|---|
| constructor | useState 初始值 | — |
| getDerivedStateFromProps | useEffect + 依赖数组 | — |
| componentDidMount | useEffect(() => { … }, []) | 空依赖数组 |
| shouldComponentUpdate | React.memo / useMemo | — |
| componentDidUpdate | useEffect(() => { … }, [deps]) | 非空依赖 |
| componentWillUnmount | useEffect(() => { return cleanup; }, [deps]) | 返回清理函数 |
Hooks 示例(模拟类组件):
import { useState, useEffect } from 'react'; function MyFunctionalComponent({ value }) { const [count, setCount] = useState(0); useEffect(() => { // componentDidMount + componentDidUpdate fetch(`/api/data?value=${value}`).then(res => setCount(res.count)); return () => { // componentWillUnmount console.log('清理资源'); }; }, [value]); // 依赖 value 变化 return <div>Count: {count}</div>; }注意:useEffect 的依赖数组是关键(空数组 = 只挂载时执行;无数组 = 每次渲染都执行)。
5. 常见问题与最佳实践
- 无限循环:componentDidUpdate / useEffect 中无条件 setState → 无限渲染。
- 性能优化:用 shouldComponentUpdate / React.memo 避免子组件不必要重渲染。
- 异步问题:setState 是异步的(用回调形式处理)。
- 迁移建议:新项目优先 Hooks(函数组件);老项目类组件可渐进迁移。
- 调试工具:React DevTools 高亮组件渲染顺序和生命周期钩子。
掌握这些,你就能在 React 项目中精准控制组件行为!如果想看具体 Hooks 示例或某个方法的深度代码实战,告诉我,我们继续。