
注:只有类组件中才有传统的生命周期钩子
🔥生命周期顺序
阶段 | 生命周期方法 | 说明 | 是否常用 |
---|---|---|---|
挂载 | constructor | 初始化 state,绑定方法等 | ✅ 一般必要 |
getDerivedStateFromProps | 派生状态,极少需要 | ❌ 不推荐 | |
render | 渲染 JSX | ✅ 必须 | |
componentDidMount | 组件挂载后执行,适合数据请求、副作用处理 | ✅ 非常常用 | |
更新 | getDerivedStateFromProps | 同上 | ❌ 很少用 |
shouldComponentUpdate | 控制是否 re-render,提高性能 | ✅ 有优化需求时用 | |
render | 再次渲染 | ✅ | |
getSnapshotBeforeUpdate | 获取更新前 DOM 状态,如滚动位置 | ⚠️ 偶尔用 | |
componentDidUpdate | 更新后执行副作用、DOM 操作等 | ✅ 很常用 | |
卸载 | componentWillUnmount | 组件卸载前执行,清理副作用 | ✅ 非常常用 |
错误 | componentDidCatch | 捕获子组件错误 | ✅ 有一定复杂度时用 |
getDerivedStateFromError | 配合上面处理错误 UI | ⚠️ 一般用得少 |
一、挂载阶段(初始化)
1. constructor(props)
类中的构造函数
- 在 React 组件挂载之前被调用。
- 初始化函数内部 state 或者在 this 上挂载方法。
- 如果使用了
constructor
,则必须在其中书写super(props)
,并将传给constructor
的props
传入super
内,否则会导致指向错误(ES6语法)。
.
2. static getDerivedStateFromProps(newprops, state)
是 React 类组件中的一个静态方法,接收新的props和先前的state,根据props的变化来更新state。
可以在组件接收新的props时自动更新state,而无需手动进行状态管理。state 的值在任何时候都取决于 props
React官方文档建议避免在组件中使用 static getDerivedStateFromProps(),会导致代码冗余,使组件难以维护
-
不能访问到组件实例
this
,指向undefined
-
在创建或更新阶段调用,或者在
props
、state
和render
方法前调用。 -
根据新的 props 和当前的 state 来更新 state。它通常用于处理 props 变化时 state 的更新逻辑。如果返回 null 则不更新任何内容。
返回值:
- 必须要有返回值,要么返回新的 state 对象,要么返回null
- 如果返回一个新的 state 对象,这个新的 state 将会与当前的 state 合并。
- 如果返回 null 来表示不更新任何 state
使用场景:
- 当 props 改变时,需要更新 state:例如,你可能有一个受控组件,其内部状态需要基于传入的 props 来更新。
- 在挂载时,根据初始 props 来设置 state:虽然可以在构造函数中设置初始 state,但有时你可能想基于传入的 props 来设置初始 state。
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 初始化 state,使用传入的 msg 作为初始值
this.state = { msg: props.initialMsg };
}
static getDerivedStateFromProps(props, state) {
// 如果传入的 msg 与当前 state 中的 msg 不同,则更新 state
if (props.msg !== state.msg) {
return { msg: props.msg };
}
// 如果传入的 msg 没有变化,则返回 null 表示不更新 state
return null;
}
render() {
// 在 render 方法中使用 state 中的 msg
return <div>{this.state.msg}</div>;
}
}
用来替代 componentWillMount()
在组件即将被挂载到页面时执行(16.3废弃告警)
.
3. render()
渲染组件,用于返回组件的 JSX。
React最重要的步骤,创建虚拟DOM,进行diff算法,更新DOM树都在此进行。此时就不能更改state了。
不要在 render()
里面使用 setState
,否则会触发死循环导致内存崩溃。
.
4. componentDidMount()
在组件被挂载到页面后立即执行,只调用一次
可以执行任何依赖于DOM的初始化操作,如数据请求、DOM 操作等
二、更新阶段
1. static getDerivedStateFromProps(newprops, state)
与挂载阶段相同,接收新的props和先前的state,根据props的变化来更新state
.
2. shouldComponentUpdate(nextProps, nextState)
-
在组件更新之前调用,并返回一个布尔值来控制组件是否进行更新:
-
返回 true 时组件更新,会继续其渲染流程。
-
返回 false 则不更新,会跳过渲染,组件将保持原样,后面所有的生命周期也都不会执行
-
如果没有这个函数,则默认返回 true
-
-
使用
this.forceUpdate()
强制刷新 会跳过shouldComponentUpdate()
生命周期方法,谨慎使用 -
不要在
shouldComponentUpdate()
中调用setState()
,否则会导致无限循环调用更新、渲染,直至浏览器内存崩溃。
参数:
- nextProps:即将接收的新 props。
- nextState:即将更新的 state。
使用场景:
主要用于性能优化。
当你知道在某些情况下组件的输出不会因 props 或 state 的变化而改变时,你可以通过返回 false 来阻止不必要的渲染。
.
3. render()
如果 shouldComponentUpdate
返回 true,或者没有定义 shouldComponentUpdate
这个方法,React将重新渲染组件。
.
4. getSnapshotBeforeUpdate(prevProps, prevState)
在最近一次渲染输出(提交到DOM节点)之前调用,即在 DOM 更新之前被调用,可以在这里获取更新前的状态,用于捕获 DOM 更新前的某些信息(如滚动位置)。
在更新之前获取快照,此用法并不常见
返回值:
- 必须要有返回值,要么返回 快照值(snapshot),要么返回null
- 快照值(snapshot)可以是任何类型的值
- 如果返回 null ,则意味着你没有为该组件的更新过程提供任何特定的快照值
- 返回的值(无论是快照值还是 null)都会作为第三个参数传递给
componentDidUpdate(prevProps, prevState, snapshot)
方法
.
5. componentDidUpdate(prevProps, prevState, snapshot)
-
在真实 DOM 更新完成后被调用。
-
可以在这里执行依赖于 DOM 更新的操作,如数据请求、DOM 操作等。
-
但不能在此方法中直接调用 `setState ,因为这可能会导致额外的更新循环。
三、卸载阶段(销毁)
1. componentWillUnmount()
-
在组件即将被卸载及销毁之前直接调用
-
通常在这里执行必要的清理操作,例如,清除定时器,取消网络请求或删除在
componentDidMount
中创建的DOM元素。
注:除了
render()
,其他所有的生命周期函数都可以没有
⚡️ 执行顺序
在父子组件中生命周期函数执行顺序:
- 挂载阶段:
- 父组件:rconstructor()
- 父组件:static getDerivedStateFromProps()
- 父组件:render()
- 子组件:rconstructor()
- 子组件:static getDerivedStateFromProps()
- 子组件:render()
- 子组件:componentDidMount()
- 父组件:componentDidMount()
.
- 更新阶段:
- 父组件:static getDerivedStateFromProps()
- 父组件:shouldComponentUpdate()
- 父组件:render()
- 父组件:getSnapshotBeforeUpdate()
- 父组件:componentDidUpdate()
- 子组件:static getDerivedStateFromProps()
- 子组件:shouldComponentUpdate()
- 子组件:render()
- 子组件:getSnapshotBeforeUpdate()
- 子组件:componentDidUpdate()
.
- 卸载阶段:
- 子组件:componentWillUnmount()
- 父组件:componentWillUnmount()
🚀函数组件中常用生命周期 Hook 对应
函数组件 Hook | 等价类组件生命周期 | 说明 | 是否常用 |
---|---|---|---|
useEffect(() => {}, []) | componentDidMount | 初次挂载副作用 | ✅ 必用 |
useEffect(() => {...}, [deps]) | componentDidUpdate | 依赖变化时触发副作用 | ✅ 必用 |
useEffect(() => {... return ...}) | componentWillUnmount | 卸载清理逻辑 | ✅ 必用 |
useLayoutEffect | 类似 componentDidMount/Update | 但在 DOM 更新前同步执行 | ⚠️ 特殊场景用 |
useRef | 存储跨 render 的状态/DOM 引用 | 非生命周期,但常用于关联 | ✅ 常用 |
useMemo / useCallback | shouldComponentUpdate 优化 | 提升性能,避免不必要 render | ✅ 优化时用 |
useErrorBoundary (第三方) | componentDidCatch | 错误边界 | ⚠️ 错误处理时用 |
React 常用 Hook 生命周期行为实战模板
import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react';
// 模拟数据请求
const fetchData = async () => {
return new Promise((resolve) => {
setTimeout(() => resolve('数据加载完成 ✅'), 1000);
});
};
const MyComponent = () => {
// 1️⃣ useState:组件状态
const [count, setCount] = useState(0);
const [data, setData] = useState('');
// 2️⃣ useRef:保存 DOM 或跨 render 不变的值
const domRef = useRef<HTMLDivElement>(null);
const isFirstRender = useRef(true); // 用于跳过首次 useEffect
// 3️⃣ useEffect:模拟 componentDidMount,初次加载请求数据
useEffect(() => {
fetchData().then((res) => {
setData(String(res));
});
// 组件卸载时清理定时器(模拟 componentWillUnmount)
return () => {
console.log('✅ 组件卸载,执行清理');
};
}, []); // 空依赖数组 => 只在挂载时运行一次
// 4️⃣ useEffect:模拟 componentDidUpdate,但跳过首次
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
console.log('🔁 count 更新了:', count);
}, [count]);
// 5️⃣ useMemo:缓存计算值(避免不必要的计算)
const doubleCount = useMemo(() => {
console.log('🧠 重新计算 doubleCount');
return count * 2;
}, [count]);
// 6️⃣ useCallback:缓存函数(避免子组件重复渲染)
const handleAdd = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return (
<div ref={domRef}>
<h2>🌟 React 生命周期 Hook 示例</h2>
<p>数据:{data || '加载中...'}</p>
<p>count:{count}</p>
<p>doubleCount(缓存):{doubleCount}</p>
<button onClick={handleAdd}>点击 +1</button>
</div>
);
};
export default MyComponent;
版本迁移
React 16.3 版本
-
componentWillMount
,componentWillReceiveProps
和componentWillUpdate
启用弃用告警,并建议开发者避免使用它们。 -
这三个生命周期因为经常会被误解和滥用,所以被称为 “不安全”(unsafe)的生命周期(unsafe 不是指安全性,而是表示使用这些生命周期的代码,有可能在未来的 React 版本中存在缺陷,可能会影响未来的异步渲染)
-
React提供了一个临时的“escape hatch”,允许开发者继续使用这些“不安全”的方法,但需要在它们前面加上
UNSAFE_
前缀
如:UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
-
新增生命周期
getDerivedStateFromProps(nextProps, prevState)
和getSnapshotBeforeUpdate(prevProps, prevState)
(用的很少)
.
React 17.0 版本
-
componentWillMount
,componentWillReceiveProps
和componentWillUpdate
仍然可以在 React 17 中使用,但React会给出警告。 -
使用
UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
,则不会警告。 -
其余的生命周期方法在React 17中仍然存在,但建议使用Hooks(如useEffect)进行替代,以实现更简洁和可维护的代码。
.
React 18.0 版本
React 18.0 版本在生命周期方法上与 React 17.0 版本类似,并没有引入新的改动。
但引入了新的特性和优化,特别是与并发模式(Concurrent Mode)和异步渲染相关的部分。
-
并发模式:
React 18引入了并发模式,允许React在渲染过程中暂停、恢复和优先处理重要的更新。这可能会影响到某些生命周期方法的执行顺序和频率。 -
异步渲染:
-
startTransition()
: 用于告诉React哪些组件的渲染可以被推迟执行。 -
useDeferredValue()
: 用于缓存某些组件的状态,以便推迟渲染。 -
useTransition()
: 用于告诉React哪些状态更新应该被推迟执行,并返回一个Promise,表示更新完成后的状态。
总结
常用的钩子:
render()
:初始化渲染或更新渲染调用componentDidMount()
:开启监听,发送ajax请求componentWillUnmount()
:做些收尾工作,如清理定时器
.
即将废弃的钩子:
componentWillMount()
componentWillReceiveProps()
componentWillUpdate()
现在使用会出现警告,下个版本需要加上UNSAFE_
前缀才能使用,以后可能会被彻底废弃,不建议使用
.
新增的钩子:
static getDerivedStateFromProps()
getSnapshotBeforeUpdate()
使用场景都比较少
函数式组件 useEffect Hook
函数式组件没有内置的生命周期钩子,但可以使用 useEffect Hook
来替代传统生命周期钩子的功能。
Hooks提供了更灵活、更简洁的方式来处理组件中的状态、副作用和逻辑,是React现代开发的重要组成部分。
useEffect 接收两个参数:
-
第一个参数(回调函数):这是一个函数,它会在组件挂载后执行,并在每次更新后重新执行(除非指定了依赖项数组)。
-
在组件挂载后执行,并在每次更新后重新执行:
useEffect(() => { console.log('Component mounted or updated'); });
-
若返回一个函数,则该函数会在组件卸载时执行。相当于
componentWillUnmount()
:useEffect(() => { const timer = setInterval(() => { console.log('Timer is running'); }, 1000); // 返回一个清理函数,在组件卸载时执行 return () => { clearInterval(timer); console.log('组件卸载前清空定时器'); }; },[]); // 由于没有依赖项,这个 effect 只在组件首次渲染时执行
-
-
第二个参数(依赖项数组):这是一个可选的数组,它包含了你的 effect 依赖的变量。
-
当数组中的某个值发生变化时,effect 才会重新执行。
只要 count 变化时,effect 回调会执行。相当于
componentDidMount()
+componentDidUpdate
:(只监听 count)useEffect(() => { console.log('Component mounted or updated'); }, [count]);
-
如果不指定这个数组,那么 effect 会在每次组件渲染后都执行。
任何 state 发生变化,effect 回调都会执行。相当于
componentDidMount()
+componentDidUpdate
:(监听所有 state)useEffect(() => { console.log('Component mounted or updated'); });
-
如果指定一个
空数组 []
,则 effect 只会在组件首次渲染时执行。只会在组件首次渲染时执行,相当于
componentDidMount()
:(不监听任何 state)useEffect(() => { console.log('Component mounted or updated'); },[]);
-
综上:useEffect Hook
可以看做是 componentDidMount()
、componentDidUpdate
、componentWillUnmount()
这三个生命周期钩子的组合。
useEffect 的强大之处在于它可以精细地控制副作用的触发时机,通过指定不同的依赖项数组来实现。这使得它在处理复杂的副作用逻辑时更加灵活和强大。