React 16 是 React 发展史上的一个重大分水岭,它引入了全新的协调引擎 Fiber,不仅重构了底层架构,还废弃/替换了一些旧的生命周期方法,以支持异步渲染(Concurrent Rendering) 和更安全的更新机制。
下面从 生命周期方法的变化 和 底层优化 两个维度详细对比:
一、React 16 之前的生命周期(Legacy 生命周期)
经典三阶段:
1. Mounting(挂载)
constructor()componentWillMount()⚠️render()componentDidMount()
2. Updating(更新)
componentWillReceiveProps(nextProps)⚠️shouldComponentUpdate(nextProps, nextState)componentWillUpdate()⚠️render()componentDidUpdate(prevProps, prevState)
3. Unmounting(卸载)
componentWillUnmount()
旧生命周期测试代码
import React from 'react';
import ReactDOM from 'react-dom';
class Son extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
console.log('constructor');
}
componentWillMount() {
console.log('componentWillMount');
}
componentDidMount() {
console.log('componentDidMount');
}
componentWillUnmount() {
console.log('componentWillUnmount');
}
componentWillReceiveProps(nextProps, nextContext) {
console.log('componentWillReceiveProps nextProps', nextProps);
console.log('componentWillReceiveProps nextContext', nextContext);
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.log('shouldComponentUpdate nextProps', nextProps);
console.log('shouldComponentUpdate nextState', nextState);
console.log('shouldComponentUpdate nextContext', nextContext);
return true;
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
add = () => {
this.setState({
count: this.state.count + 1
});
};
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
};
update = () => {
this.forceUpdate();
};
render() {
console.log('render');
return (
<fieldset>
<legend>count: {this.state.count}</legend>
<button onClick={this.add}>Count + 1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.update}>强制更新组件</button>
</fieldset>
);
}
}
class Parent extends React.Component {
componentWillUnmount() {
console.log('Parent - componentWillUnmount');
}
render() {
return (
<div>
<p>父组件</p>
<Son />
</div>
);
}
}
export default Parent;
二、React 16.3+ 引入的新生命周期(Modern Lifecycle)
主要变化:废弃三个“will”方法,引入两个新方法
| 旧方法(不安全) | 新替代方案 |
|---|---|
componentWillMount | → 移入 constructor 或 componentDidMount |
componentWillReceiveProps | → static getDerivedStateFromProps |
componentWillUpdate | → getSnapshotBeforeUpdate(配合 componentDidUpdate) |
✅ 新生命周期完整流程:
Mounting:
constructor()static getDerivedStateFromProps(props, state)(首次渲染也调用)render()componentDidMount()
Updating:
static getDerivedStateFromProps(props, state)shouldComponentUpdate()render()getSnapshotBeforeUpdate(prevProps, prevState)← 新增componentDidUpdate(prevProps, prevState, snapshot)
Unmounting:
componentWillUnmount()
getDerivedStateFromProps(props, state)
static getDerivedStateFromProps(props, state) 是一个静态生命周期方法,用于在组件接收到新的 props 时,安全地派生(derive)state。它的设计目的是替代不安全的 componentWillReceiveProps,并确保与 React 的异步渲染(Concurrent Rendering)兼容。
✅ 关键特性:
| 特性 | 说明 |
|---|---|
| 静态方法 | 不能访问 this(无组件实例) |
| 纯函数 | 不能包含副作用(如 API 调用、订阅) |
| 每次渲染都调用 | 包括首次挂载和后续更新 |
| 必须返回对象或 null | 返回值会浅合并到 state |
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate(prevProps, prevState) 是 React 16.3 引入的一个生命周期方法,它的核心作用是:
在 DOM 更新(commit)之前,捕获一些可能在更新后丢失的信息(如滚动位置、尺寸等),并将这个“快照”传递给
componentDidUpdate。
🌰 典型场景:聊天窗口保持滚动位置
- 用户正在看聊天记录底部。
- 新消息来了,列表变长。
- 如果直接滚动到底部,会打断用户阅读。
- 理想行为:如果用户在底部,则自动滚动到底;否则保持当前位置。
示例代码:
class ScrollingList extends React.Component {
listRef = React.createRef();
// ✅ 在 DOM 更新前调用
getSnapshotBeforeUpdate(prevProps, prevState) {
// 检查列表是否滚动到底部
const list = this.listRef.current;
const isAtBottom = list.scrollHeight - list.scrollTop === list.clientHeight;
// 返回“快照” → 会被传给 componentDidUpdate
return isAtBottom;
}
// ✅ 在 DOM 更新后调用,接收 snapshot
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot) {
// 如果更新前在底部,则滚动到底部
const list = this.listRef.current;
list.scrollTop = list.scrollHeight;
}
}
render() {
return (
<div ref={this.listRef} style={{ height: '200px', overflow: 'auto' }}>
{this.props.items.map(item => <div key={item.id}>{item.text}</div>)}
</div>
);
}
}
✅ 关键特性:
| 特性 | 说明 |
|---|---|
| 调用时机 | 在 render 之后、DOM 更新之前(commit 阶段开始前) |
| 返回值 | 任意值(通常为对象、布尔值、数字等),会作为 第三个参数 传给 componentDidUpdate |
必须配合 componentDidUpdate | 单独使用无意义,快照需在更新后处理 |
| 可以访问 DOM | 此时旧 DOM 仍存在,可安全读取布局信息 |
执行流程图解
父组件更新
↓
[render 阶段]
→ render() 返回新虚拟 DOM
↓
[commit 阶段开始]
→ getSnapshotBeforeUpdate(prevProps, prevState)
→ 读取旧 DOM 状态(如 scrollTop)
→ 返回 snapshot
↓
→ React 更新真实 DOM
↓
→ componentDidUpdate(prevProps, prevState, snapshot)
→ 使用 snapshot 执行副作用(如滚动)
💡 关键点:
getSnapshotBeforeUpdate是 唯一能在 DOM 更新前读取旧 DOM 状态 的生命周期方法。
新生命周期测试代码
import React, { Component } from 'react';
class Son extends Component {
state = {
msg: '这是子组件'
};
static getDerivedStateFromProps(nextProps, prevState) {
console.group('子组件 - shouldComponentUpdate');
console.log(nextProps, prevState);
console.groupEnd('子组件 - shouldComponentUpdate');
return nextProps.num > 0.5 ? { num: nextProps.num } : null;
}
render() {
return (
<div style={{ margin: 10, padding: 10, border: '1px solid red' }}>
<p>{this.state.msg}</p>
<p>子组件接收父组件的 num 值(子组件大于 0.5 时更新):{this.state.num}</p>
</div>
);
}
}
class Fa extends Component {
state = {
name: '这是父组件',
num: '这是父组件给子组件传递的值',
arr: [1, 2, 3, 4, 5]
};
static getDerivedStateFromProps(nextProps, prevState) {
console.group('父组件 - getDerivedStateFromProps');
console.log(nextProps);
console.log(prevState);
console.groupEnd('父组件 - getDerivedStateFromProps');
/**
* 这里返回 null 或者 一个对象
* 当需要更新状态时,需要返回一个对象,这个对象的内容,会添加到 state 中,如果 state 中已经包含某属性,则覆盖原来的属性
* 当不需要更新状态时,需要返回 null
*/
return {
name: 'getDerivedStateFromProps 覆盖原name属性',
notDefine: '未在state声明,此时添加到 state'
};
}
componentDidMount() {
console.group('父组件 - componentDidMount');
console.log(this.state);
console.groupEnd('父组件 - componentDidMount');
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.group('父组件 - shouldComponentUpdate');
console.log(nextProps);
console.log(nextState);
console.log(nextContext);
console.groupEnd('父组件 - shouldComponentUpdate');
// 判断是否 rerender
return nextState;
}
/**
* 获取上次的 props 和 state 数据
* 并且返回 null 或者 object,然后将返回值作为 snapshot,传递给 componentDidUpdate第三个参数
*/
getSnapshotBeforeUpdate(prevProps, prevState) {
console.group('父组件 - getSnapshotBeforeUpdate');
console.log(prevProps);
console.log(prevState);
console.groupEnd('父组件 - getSnapshotBeforeUpdate');
return {
msg: 'from getSnapshotBeforeUpdate'
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.group('父组件 - componentDidUpdate');
console.log(prevProps);
console.log(prevState);
console.log(snapshot);
console.groupEnd('父组件 - componentDidUpdate');
}
changeStr = () => {
this.setState({
num: Math.random()
});
};
render() {
const { name, num, arr } = this.state;
return (
<div style={{ margin: 10, padding: 10, border: '1px solid blue' }}>
{name}:<button onClick={this.changeStr.bind(this)}>改变 num 的值</button>
<p>这是父组件的 num 值: {num}</p>
<Son
num={num}
arr={arr}
></Son>
</div>
);
}
}
export default Fa;
三、为什么废弃旧的 “will” 方法?
核心原因:它们与异步渲染(Concurrent Mode)不兼容
❌ 问题 1:componentWillMount / componentWillUpdate 可能被多次调用
- 在 Fiber 架构下,React 可能暂停、中断、重启渲染过程。
- 这些方法可能执行多次,导致:
- 重复订阅(内存泄漏)
- 多次 API 调用(副作用不可控)
✅ 解决方案:
- 初始化逻辑 → 放到
constructor(只执行一次)- 副作用(如 API 调用)→ 放到
componentDidMount(保证只执行一次)
❌ 问题 2:componentWillReceiveProps 容易引发 bug
- 开发者常在这里做状态派生,但容易写出反模式:
// 危险!props 变化时重置 state,但可能覆盖用户输入
componentWillReceiveProps(nextProps) {
if (nextProps.userID !== this.props.userID) {
this.setState({ comments: [] }); // 丢失未保存的草稿!
}
}
✅ 解决方案:
使用static getDerivedStateFromProps(纯函数,无副作用):static getDerivedStateFromProps(props, state) {
if (props.userID !== state.prevUserID) {
return {
prevUserID: props.userID,
comments: [],
};
}
return null; // 不更新 state
}
❌ 问题 3:无法获取 DOM 更新前的状态
- 比如滚动位置、尺寸等,在
componentDidUpdate中已失效。
✅ 解决方案:
新增getSnapshotBeforeUpdate:getSnapshotBeforeUpdate(prevProps, prevState) {
// 在 DOM 更新前读取 scrollHeight
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot; // 保持滚动位置
}
}
四、React 16 的核心优化:Fiber 架构
🧠 什么是 Fiber?
- Fiber 是 React 16 重写的协调(Reconciliation)算法。
- 将原本递归的同步渲染改为可中断的链表遍历。
✅ 关键优化:
| 优化点 | 说明 |
|---|---|
| 可中断渲染 | 高优先级任务(如用户输入)可打断低优先级更新(如后台数据加载) |
| 时间切片(Time Slicing) | 将长任务拆分为小块,在浏览器空闲时执行,避免卡顿 |
| 错误边界(Error Boundaries) | 新增 componentDidCatch,防止组件错误导致整个应用崩溃 |
| Portals | 通过 ReactDOM.createPortal 将子节点渲染到 DOM 树任意位置(如 Modal) |
| Fragment | 支持返回多个元素而无需包裹 <div>(<>...</>) |
💡 Fiber 的目标:让 React 应用更流畅、响应更快,尤其在低端设备上。
五、生命周期对比表(一目了然)
| 阶段 | React ≤15 | React ≥16.3(推荐) | 说明 |
|---|---|---|---|
| 挂载前 | componentWillMount | — | 已废弃 |
| 接收新 props | componentWillReceiveProps | static getDerivedStateFromProps | 纯函数,无副作用 |
| 更新前 | componentWillUpdate | getSnapshotBeforeUpdate | 获取 DOM 更新前快照 |
| 错误捕获 | — | componentDidCatch | 新增错误边界 |
| 渲染 | render | render | 不变 |
| 挂载后 | componentDidMount | componentDidMount | 不变 |
| 更新后 | componentDidUpdate | componentDidUpdate | 接收 snapshot 参数 |
| 卸载 | componentWillUnmount | componentWillUnmount | 不变 |
总结
| 方面 | React 16 之前 | React 16+ |
|---|---|---|
| 架构 | Stack Reconciler(同步递归) | Fiber Reconciler(异步可中断) |
| 生命周期 | 包含不安全的 “will” 方法 | 安全、可预测的新方法 |
| 渲染性能 | 长任务阻塞主线程 | 时间切片,高优先级任务优先 |
| 错误处理 | 组件错误导致白屏 | 错误边界隔离错误 |
| 开发体验 | 类组件为主 | 函数组件 + Hooks 成主流 |
💡 本质:React 16 不只是增加功能,而是为未来异步渲染和并发模式打下基础。生命周期的变更,是为了让开发者写出更健壮、可预测的代码。
如今(React 18+),函数组件 + Hooks 已成为首选,类组件生命周期逐渐成为“历史知识”,但理解其演进逻辑对深入掌握 React 至关重要。