React 生命周期:Render、Commit 与 Effect 清理
本文目录
更新说明: 这篇旧笔记发表于 2018 年,主要讨论类组件生命周期。现代 React 仍支持类组件,但新代码通常使用函数组件与 Hooks。两者的生命周期模型不能机械地一一对应;本文把渲染阶段、DOM 提交和外部系统清理分开说明,原始笔记保留在折叠区。
一张准确的类组件时序图
挂载
constructor
-> static getDerivedStateFromProps(如有)
-> render
-> React 提交 DOM 变更
-> componentDidMount
更新
static getDerivedStateFromProps(如有)
-> shouldComponentUpdate(如有;返回 false 时通常跳过后续更新阶段)
-> render
-> getSnapshotBeforeUpdate(读取 DOM 改变前的信息)
-> React 提交 DOM 变更
-> componentDidUpdate
卸载
componentWillUnmount
-> React 从 DOM 移除节点
这些顺序帮助定位副作用边界,但不要把 React 内部调度时机当作同步调用承诺。render 可能被重复调用或放弃;render、constructor 和 getDerivedStateFromProps 都不应订阅、发请求、改 DOM 或写全局状态。
按职责理解类组件 API
| API | 适合做什么 | 不应该做什么 |
|---|---|---|
| constructor | 初始化实例字段和初始 state | 发请求、注册全局订阅 |
| render | 依据当前 props/state 描述 UI | 修改已有 DOM、产生外部副作用 |
| componentDidMount | 接入需要 DOM 的库、建立订阅或发起必要请求 | 假设组件以后不会再次挂载 |
| componentDidUpdate | 在 props/state 改变后同步外部系统 | 无条件 setState,造成更新循环 |
| getSnapshotBeforeUpdate | 在 React 改动 DOM 前读取滚动位置等快照 | 执行一般业务副作用 |
| componentWillUnmount | 取消订阅、清理计时器、释放连接 | 假设异步任务一定能被撤回 |
| shouldComponentUpdate | 在测量确认后跳过不必要的类组件更新 | 保证业务逻辑执行或阻止安全校验 |
getSnapshotBeforeUpdate 返回的值会作为第三个参数传给 componentDidUpdate;它解决的是“DOM 更新前读取、DOM 更新后使用”的特定时序问题。普通更新后的 DOM 操作通常在 componentDidUpdate 中进行。
getDerivedStateFromProps 会在初次挂载和更新阶段参与状态推导。它是静态方法,只有在确实需要根据 prop 变化重置或派生本地状态时才考虑;复制一份 props 到 state 往往会造成双重数据源。优先通过 render 直接使用 prop,或由父组件拥有状态。
旧生命周期为什么带 UNSAFE_
componentWillMount、componentWillReceiveProps、componentWillUpdate 属于旧生命周期。它们在旧版同步渲染下常被用于初始化和更新副作用,但在可中断或重试的渲染模型中,副作用可能重复或发生在不合适的时机。兼容旧代码时会看到 UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate;新代码不要依赖它们。
常见迁移方向:
- 初始化外部订阅移到 componentDidMount,并在 componentWillUnmount 清理。
- 由 props 派生 state 前先判断能否在 render 中直接计算;确有状态重置需求时明确身份和重置条件。
- 更新时同步外部系统放在 componentDidUpdate,并比较相关的前后值。
- 只为性能跳过更新时先测量;不要用 shouldComponentUpdate 修复错误的数据流。
函数组件与 Effect
函数组件没有与每个类生命周期逐一对应的 Hook。useEffect 表达的是“让外部系统与本次已提交的 React 状态保持同步”;依赖变化时,React 先运行上一次 cleanup,再运行新的 setup,卸载时运行最后一次 cleanup。
function OnlineStatus({ userId }) {
React.useEffect(() => {
const subscription = subscribeToStatus(userId, (status) => {
updateStatusView(status);
});
return () => subscription.unsubscribe();
}, [userId]);
return <p>查看用户 {userId} 的在线状态</p>;
}
Effect 应完整描述它读取的响应式依赖,并且 setup/cleanup 成对。开发环境的 Strict Mode 会额外执行 setup/cleanup 检查,目的是发现重复订阅和不完整清理;不要用 ref 强行压成“只执行一次”来掩盖资源泄漏。
不是所有副作用都应该放在 Effect:由用户点击触发的购买、提交或导航应放在事件处理器;由 props/state 推导的显示值直接计算;需要请求数据时,要处理取消或忽略旧响应,并考虑路由加载器或数据缓存层。取消浏览器请求不等于服务端没有收到它。
错误边界
类组件可通过 static getDerivedStateFromError 更新降级 UI,并用 componentDidCatch 记录错误。错误边界主要捕获其子树渲染过程中的错误,不会自动捕获事件处理器中的异常、任意异步回调异常或服务端渲染错误;这些路径需要各自处理。
验证方式
- 用 React DevTools 检查组件挂载、更新和卸载是否符合预期。
- 在 Strict Mode 下重复进入、离开页面,观察订阅、事件监听和计时器是否被清理。
- 快速切换 props,检查旧网络响应是否可能覆盖新结果。
- 只有 Profiler 证明更新成本值得优化时,才引入 shouldComponentUpdate、PureComponent 或 memo。
- 将真实浏览器 DOM 操作与纯 render 分开测试,不依赖某个实现版本的内部调用次数。
官方资料
展开查看 2018 年原始生命周期笔记
## react生命周期
## class组件(类)组件的生命周期
### 挂载(当组件实例被创建并插入DOM中)的时候 生命周期的调用顺序
1. 构造函数 constructor() 初始化状态
`
static getDerivedStateFromProps(nextprops,prevstate)
根据新的属性(props)生成新的状态(state)
参数是新的属性props和老的状态,此生命周期不常用
`
1. render() 把虚拟dom变成真实dom并插入到dom元素中
2. componentDidMount() dom挂载完成
- 组件的渲染顺序 父组件先渲染(父组件render之后) 子组件开始渲染 子组件挂载完成之后(componentDidMount)父组件最后挂载完成(componentDidMount)
### 组件更新 组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下
1. static getDerivedStateFromProps()
2. shouldComponentUpdate()
- 如果 shouldComponentUpdate返回的是false 则不在继续
3. render()
4. getSnapshotBeforeUpdate() 获取dom更新前的快照
5. componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
#### 卸载 当组件从 DOM 中移除时会调用如下方法
componentWillUnmount()
#### 生命周期流程(有更新)
construtor -> getDerivedStateFromProps(静态方法)-> 询问是否需要更新->shouldComponentUpdate不需要(中断)
5. componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
construtor -> getDerivedStateFromProps(静态方法)-> 询问是否需要更新->shouldComponentUpdate需要-> render(渲染)->getSnapshotBeforeUpdate(获取到更新的dom) -> componentDidUpdate
## Render 阶段(Render及之前的生命周期)纯净且不包含副作用。可能会被 React 暂停,中止或重新启动
construtor render 初始化状态和接收props参数
## “Pre-commit 阶段”
可以读取 DOM。 getSnapshotBeforeUpdate
## “commit 阶段” 可以使用 DOM,运行副作用,安排更新。(重点)
componentDidMount componentDidUpdate
componentWillUnmount
## static getDerivedStateFromError()
组件报错会调用这个生命周期,参数是error
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。