React 组件生命周期
1. 组件生命周期
基本概念
每个 React 组件都像一个生命体,从诞生(被创建并添加到 DOM)到成长(通过更新 props 和 state 来改变),再到最终的消亡(从 DOM 中移除)。React 提供了一系列特殊的“生命周期方法”,允许我们在组件生命中的特定时刻执行代码。
React 的组件生命周期的三个核心阶段如下:
- 挂载 (Mounting): 组件实例被创建并插入到 DOM 中的过程。
- 更新 (Updating): 当组件的
props
或state
发生变化,导致需要重新渲染时的过程。 - 卸载 (Unmounting): 当组件实例从 DOM 中被移除时的过程。
除此之外,还有一个特殊的阶段:错误处理 (Error Handling),用于捕获并处理子组件发生的错误。
下面是一个组件生命周期图谱:

挂载阶段
当组件实例被创建并插入到 DOM 中时,以下方法会按顺序被调用。这个阶段只发生一次。
constructor(props)
:它完成了如下工作:- 通过给
this.state
赋值来初始化组件的内部 state。 - 为事件处理函数绑定
this
。
- 通过给
注意: 这是唯一可以直接对
this.state
赋值的地方。在所有其他方法中,都必须使用this.setState()
。
constructor(props) {
super(props); // 必须先调用 super(props)
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
static getDerivedStateFromProps(props, state)
:它在组件实例化后、渲染前调用,以及在后续更新中也会被调用。它允许组件根据props
的变化来更新其内部state
。它具有如下特点:- 它是一个静态方法,无法访问组件实例的
this
。 - 它应该返回一个对象来更新
state
,或者返回null
表示无需更新。
- 它是一个静态方法,无法访问组件实例的
- 这是一个不常用的方法,适用于
state
的值完全由props
决定(受控组件)的罕见情况。
警告: 过度使用此方法通常会导致代码冗余和复杂化。在需要它之前,应该先考虑是否有更简单的解决方案,例如:将组件完全重构为受控组件,或使用
key
prop 来重置整个组件。
-
render()
: 这是唯一必须的类组件方法。React 调用此方法来获取要渲染到屏幕上的 UI 描述(JSX)。render
方法必须是纯函数。这意味着在不改变props
和state
的前提下,每次调用都应返回相同的结果,并且不应包含任何副作用(如 API 请求)。 -
componentDidMount()
:它在组件已经被渲染到 DOM 中之后立即调用。它是执行需要 DOM 节点或数据加载等副作用操作的完美时机。下面这些场景会使用它:- 网络请求: 从服务器获取数据。
- 设置订阅: 如
setInterval
或addEventListener
。 - DOM 操作: 与需要计算尺寸或位置的 DOM 元素进行交互。
componentDidMount() {
console.log('Component has mounted!');
const userID = this.props.userID;
fetch(`https://api.example.com/users/${userID}`)
.then(res => res.json())
.then(user => this.setState({ user }));
}
更新阶段
当组件的 props
或 state
发生变化时,会触发更新。更新的方法如下:
shouldComponentUpdate(nextProps, nextState)
:这是一个性能优化的关键方法。React 在重新渲染前会调用它,让我们有机会告诉 React 本次更新是否非必要。它的返回值如下:- 返回
true
(默认值): 继续执行更新流程(调用render
等)。 - 返回
false
: 跳过本次更新,render()
不会被调用。
- 返回
注意: 可以在此方法中比较
this.props
与nextProps
,以及this.state
与nextState
,来决定是否需要重新渲染。
为了方便,可以让组件继承自
React.PureComponent
而不是React.Component
。PureComponent
会自动为我们实现一个shouldComponentUpdate
,它会对props
和state
进行浅层比较。
-
render()
:如果shouldComponentUpdate
返回true
,render()
方法会再次被调用,以获取最新的 UI。 -
getSnapshotBeforeUpdate(prevProps, prevState)
:在render
之后,但在最终渲染结果提交到 DOM 之前调用。它使得组件可以在 DOM 可能发生变化(例如,列表增加项目)之前从中捕获一些信息(如滚动位置)。此方法返回的任何值都将作为第三个参数传递给componentDidUpdate()
。 -
componentDidUpdate(prevProps, prevState, snapshot)
:在组件更新被提交到 DOM 后立即调用。此方法不适用于首次渲染。它一般用于下面的场合:- 当
props
变化时执行网络请求。 - 根据 DOM 的变化执行某些操作。
- 当
重要: 在
componentDidUpdate
中调用setState
必须包裹在一个条件语句中,否则会导致无限循环的重新渲染!
componentDidUpdate(prevProps, prevState) {
// 检查 props.userID 是否真的改变了
if (this.props.userID !== prevProps.userID) {
// 如果不加条件判断,每次 setState 都会触发 componentDidUpdate,造成死循环
this.fetchData(this.props.userID);
}
}
卸载阶段
componentWillUnmount()
:在组件即将被从 DOM 中移除并销毁之前调用。这是执行所有必要清理操作的理想场所。常见场景如下- 清除在
componentDidMount
中创建的定时器 (clearInterval
)。 - 移除事件监听器 (
removeEventListener
)。 - 取消网络请求或任何挂起的订阅,以防止内存泄漏。
- 清除在
componentWillUnmount() {
console.log('Component will unmount. Cleaning up...');
clearInterval(this.timerID);
this.props.api.unsubscribe();
}
2. 在 useEffect
中的使用
函数组件中的 useEffect
Hook 巧妙地将 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的功能整合在了一起。
-
模拟
componentDidMount
:useEffect(() => { /* ... */ }, [])
(依赖项数组为空) -
模拟
componentDidUpdate
:useEffect(() => { /* ... */ }, [dep1, dep2])
(依赖项数组有值) -
模拟
componentWillUnmount
:useEffect
返回的清理函数。
// 函数组件
useEffect(() => {
// 这部分代码在挂载时 (componentDidMount) 和 a, b 更新时 (componentDidUpdate) 运行
console.log('Effect running...');
// 返回的函数在卸载时 (componentWillUnmount) 和下一次 effect 运行前运行
return () => {
console.log('Cleanup...');
};
}, [a, b]); // 依赖项数组
3. 已废弃的生命周期方法
UNSAFE_componentWillMount()
: 在render
之前调用。现在应使用constructor
或componentDidMount
。UNSAFE_componentWillReceiveProps()
: 在 props 改变时调用。现在应使用getDerivedStateFromProps
。UNSAFE_componentWillUpdate()
: 在render
之前、shouldComponentUpdate
之后调用。现在应使用getSnapshotBeforeUpdate
。