跳到主要内容

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→ 移入 constructorcomponentDidMount
componentWillReceivePropsstatic getDerivedStateFromProps
componentWillUpdategetSnapshotBeforeUpdate(配合 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 ≤15React ≥16.3(推荐)说明
挂载前componentWillMount已废弃
接收新 propscomponentWillReceivePropsstatic getDerivedStateFromProps纯函数,无副作用
更新前componentWillUpdategetSnapshotBeforeUpdate获取 DOM 更新前快照
错误捕获componentDidCatch新增错误边界
渲染renderrender不变
挂载后componentDidMountcomponentDidMount不变
更新后componentDidUpdatecomponentDidUpdate接收 snapshot 参数
卸载componentWillUnmountcomponentWillUnmount不变

总结

方面React 16 之前React 16+
架构Stack Reconciler(同步递归)Fiber Reconciler(异步可中断)
生命周期包含不安全的 “will” 方法安全、可预测的新方法
渲染性能长任务阻塞主线程时间切片,高优先级任务优先
错误处理组件错误导致白屏错误边界隔离错误
开发体验类组件为主函数组件 + Hooks 成主流

💡 本质:React 16 不只是增加功能,而是为未来异步渲染和并发模式打下基础。生命周期的变更,是为了让开发者写出更健壮、可预测的代码。

如今(React 18+),函数组件 + Hooks 已成为首选,类组件生命周期逐渐成为“历史知识”,但理解其演进逻辑对深入掌握 React 至关重要。