React 新旧生命周期对比

核心分界线:React 16.3(引入 Fiber 架构)


核心变化概览

变化可概括为:废弃 3 个 will 钩子,新增 2 个钩子,保留核心流程不变。

变更类型 旧版钩子 新版替代 说明
废弃 componentWillMount --- 挂载前可能执行多次,不安全
废弃 componentWillReceiveProps getDerivedStateFromProps 用静态方法替代,避免副作用
废弃 componentWillUpdate getSnapshotBeforeUpdate 更新前 DOM 快照捕获
新增 --- getDerivedStateFromProps 静态方法,根据 props 派生 state
新增 --- getSnapshotBeforeUpdate 获取更新前 DOM 状态,返回值传给 componentDidUpdate
保留 constructor constructor 初始化状态
保留 render render 渲染 UI
保留 shouldComponentUpdate shouldComponentUpdate 性能优化阀门
保留 componentDidMount componentDidMount 挂载完成,发请求、订阅
保留 componentDidUpdate componentDidUpdate(prevProps, prevState, snapshot) 更新完成,新增 snapshot 参数
保留 componentWillUnmount componentWillUnmount 清理工作

💡 一句话总结 :旧版的 componentWillMountcomponentWillReceivePropscomponentWillUpdate 被废弃,取而代之的是 getDerivedStateFromPropsgetSnapshotBeforeUpdate。其余生命周期钩子基本保持不变。


旧版生命周期(React 16.3 之前)

挂载阶段

kotlin 复制代码
constructor → componentWillMount → render → componentDidMount

更新阶段

触发方式 执行顺序
setState() shouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate
props 变化 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate
forceUpdate() 跳过 shouldComponentUpdate,直接 componentWillUpdaterendercomponentDidUpdate

卸载阶段

复制代码
componentWillUnmount

新版生命周期(React 16.3+)

挂载阶段

kotlin 复制代码
constructor → getDerivedStateFromProps → render → componentDidMount

更新阶段

scss 复制代码
getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate(prevProps, prevState, snapshot)

卸载阶段

复制代码
componentWillUnmount

新旧生命周期对比总表

生命周期 状态 旧版行为 新版行为
constructor 保留 初始化 state,绑定事件处理器 功能不变
componentWillMount 废弃 组件挂载前调用,可执行副作用 移除。副作用移至 componentDidMount
componentWillReceiveProps 废弃 props 变化时调用,可执行副作用和 setState 替换为 getDerivedStateFromProps(静态方法,无 this
getDerivedStateFromProps 新增 --- 静态方法,接收 propsstate,返回对象更新 state 或 null
shouldComponentUpdate 保留 返回 false 阻止更新 功能不变,性能优化阀门
componentWillUpdate 废弃 更新前调用,可读取 DOM 状态 替换为 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 新增 --- 更新前获取 DOM 快照,返回值传给 componentDidUpdate
render 保留 返回 JSX / null 功能不变
componentDidMount 保留 挂载完成,发请求、订阅 功能不变
componentDidUpdate 保留 接收 prevPropsprevState 新增第三个参数 snapshot(来自 getSnapshotBeforeUpdate
componentWillUnmount 保留 清理定时器、取消订阅 完全不变

为什么废弃 3 个 will 钩子?

⚠️ 根本原因:Fiber 架构的异步渲染

React 16 引入 Fiber Reconciler,将渲染拆分为可中断、可恢复的小任务单元。这导致 will 系列钩子在异步渲染中可能被多次调用,产生不可预期的副作用。

Stack Reconciler vs Fiber Reconciler

特性 Stack Reconciler(旧版) Fiber Reconciler(新版)
渲染方式 深度优先递归同步渲染 将渲染拆分为小任务单元
可中断性 一旦开始无法中断 可中断、可恢复、可优先级调度
will 钩子执行次数 只执行一次,行为确定 可能被执行多次

具体问题场景

重复副作用问题 :假设在 componentWillMount 中发起 API 请求,如果渲染被中断后重新开始,请求会被重复发送,造成资源浪费和数据不一致。

javascript 复制代码
// ❌ 危险:componentWillMount 中发请求
componentWillMount() {
    fetch('/api/data').then(res => {
        this.setState({ data: res.data });
    });
    // 如果渲染中断重启,请求会被重复发送!
}
javascript 复制代码
// ✅ 正确:componentDidMount 中发请求
componentDidMount() {
    fetch('/api/data').then(res => {
        this.setState({ data: res.data });
    });
    // 只在挂载完成时执行一次,安全
}

Fiber 架构与生命周期变更

React 团队的决策逻辑

废弃的钩子 在 Fiber 中的问题 解决方案
componentWillMount 可能被多次调用,副作用不可控 移除,副作用移至 componentDidMount
componentWillReceiveProps 可执行 setState 和副作用,在 Fiber 中行为不可预期 替换为纯函数 getDerivedStateFromProps
componentWillUpdate 可读取 DOM,但 Fiber 中 DOM 可能尚未准备好 替换为 getSnapshotBeforeUpdate 获取快照

💡 设计原则 :React 团队将生命周期分为渲染阶段 (Render Phase)和提交阶段 (Commit Phase)。will 钩子位于渲染阶段,可能被多次调用;did 钩子位于提交阶段,保证只执行一次。


getDerivedStateFromProps 详解

基本用法

javascript 复制代码
class Example extends React.Component {
    static getDerivedStateFromProps(nextProps, prevState) {
        // 纯函数:不能访问 this
        // 根据 props 派生 state
        if (nextProps.userId !== prevState.prevUserId) {
            return {
                prevUserId: nextProps.userId,
                userData: null  // 触发重新加载
            };
        }
        return null;  // 不更新 state
    }

    state = {
        prevUserId: null,
        userData: null
    };
}

与 componentWillReceiveProps 对比

🔴 componentWillReceiveProps(旧版)

javascript 复制代码
componentWillReceiveProps(nextProps) {
    // ❌ 可以访问 this
    // ❌ 可以执行副作用
    // ❌ 可以 setState
    if (nextProps.id !== this.props.id) {
        this.setState({ loading: true });
        fetchData(nextProps.id); // 副作用!
    }
}

🟢 getDerivedStateFromProps(新版)

javascript 复制代码
static getDerivedStateFromProps(nextProps, prevState) {
    // ✅ 纯函数,无 this
    // ✅ 无副作用
    // ✅ 只能返回对象或 null
    if (nextProps.id !== prevState.prevId) {
        return { prevId: nextProps.id, loading: true };
    }
    return null;
}

⚠️ 使用建议getDerivedStateFromProps 容易导致代码复杂化。官方建议优先考虑以下替代方案:

  1. 完全受控组件:用 props 直接控制,不使用 state
  2. 完全非受控组件 :使用 key 属性重置组件状态
  3. 使用 Hooks :函数组件中 useEffect 更灵活

getSnapshotBeforeUpdate 详解

典型场景:保存滚动位置

javascript 复制代码
class ChatList extends React.Component {
    getSnapshotBeforeUpdate(prevProps, prevState) {
        // 更新前记录列表底部距离
        if (prevProps.messages.length < this.props.messages.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;
        }
    }
}

执行时机

  1. render() 输出新的虚拟 DOM
  2. getSnapshotBeforeUpdate() 在 DOM 更新前执行,可读取当前 DOM 状态
  3. React 将变更提交到真实 DOM
  4. componentDidUpdate(prevProps, prevState, snapshot) 接收快照值

Hooks 替代方案(React 16.8+)

随着 Hooks 的引入,函数组件可以完全替代类组件的生命周期管理。

类组件生命周期 函数组件 Hook 说明
constructor useState / useReducer 初始化状态
componentDidMount useEffect(() => {}, []) 空依赖数组,只执行一次
componentDidUpdate useEffect(() => {}, [deps]) 指定依赖数组
componentWillUnmount useEffect(() => { return () => {} }, []) 返回清理函数
shouldComponentUpdate React.memo + useMemo / useCallback 性能优化
getDerivedStateFromProps 直接在组件体中计算派生值 或使用 useMemo
getSnapshotBeforeUpdate 自定义 Hook + ref 需手动实现

Hook 示例

javascript 复制代码
function Example({ userId }) {
    const [userData, setUserData] = useState(null);

    // 替代 componentDidMount + componentDidUpdate
    useEffect(() => {
        let cancelled = false;
        fetchUser(userId).then(data => {
            if (!cancelled) setUserData(data);
        });

        // 替代 componentWillUnmount
        return () => { cancelled = true; };
    }, [userId]); // 依赖变化时重新执行

    return <div>{userData?.name}</div>;
}

最佳实践

1. 副作用放在正确的位置

场景 推荐钩子 不推荐
初始化发请求、订阅 componentDidMount componentWillMount(废弃)
props 变化时同步 state getDerivedStateFromProps(谨慎) componentWillReceiveProps(废弃)
更新前保存 DOM 信息 getSnapshotBeforeUpdate componentWillUpdate(废弃)
性能优化(避免不必要渲染) shouldComponentUpdate / React.memo ---
清理定时器、取消订阅 componentWillUnmount ---

2. 避免常见陷阱

🚫 不要在 getDerivedStateFromProps 中执行副作用

这是一个静态方法,无法访问 this,也不能发请求或操作 DOM。它应该只用于根据 props 计算并返回新的 state。
⚠️ 谨慎使用派生 state

大多数 getDerivedStateFromProps 的使用场景都可以用更好的方式替代。在引入它之前,先问自己:这个 state 真的需要派生吗?


迁移建议

逐步迁移策略

迁移检查清单:

  1. 第一步 :识别代码中使用的 UNSAFE_ 前缀方法(如 UNSAFE_componentWillMount
  2. 第二步 :将 componentWillMount 中的逻辑移至 componentDidMount
  3. 第三步 :将 componentWillReceiveProps 替换为 getDerivedStateFromProps 或重构为完全受控/非受控组件
  4. 第四步 :将 componentWillUpdate 替换为 getSnapshotBeforeUpdate
  5. 第五步(可选):考虑将类组件重构为函数组件 + Hooks

使用 codemod 自动迁移

bash 复制代码
# React 官方提供的 codemod,自动重命名废弃的生命周期方法
npx react-codemod rename-unsafe-lifecycles

💡 长期建议 :对于新项目,优先使用函数组件 + Hooks。类组件的生命周期概念在维护老代码时仍然重要,但新开发应拥抱 Hooks 带来的更简洁、更可组合的状态管理方案。

相关推荐
金融小白数据分析之路13 小时前
绍兴市镇街echarts 制作
前端·数据库·echarts
Slice_cy14 小时前
Mint 自研框架设计与实现:从重复开发走向配置驱动(二)
前端·后端·架构
greenbbLV14 小时前
积分兑换商城供应商挑选:避坑要点与靠谱选择解析
前端·小程序
a11177614 小时前
3D 建筑编辑器 Pascal Editor THreeJS 开源
前端·3d·html
jason_yang14 小时前
写给女儿的英语App
前端·人工智能
思码梁田14 小时前
CSS 定位详解:从相对到固定,掌握网页布局的核心
前端·javascript·css
勇往直前plus14 小时前
Vite :从双击 HTML 到现代前端开发
前端·html
muddjsv14 小时前
CSS 高级选择器精讲:伪类、伪元素、逻辑伪类与选择器解耦规范
前端·css
HH‘HH14 小时前
前端应用的离线暂停更新策略:原理、实现与最佳实践
开发语言·前端·php
软件开发技术深度爱好者14 小时前
HTML5实现数学函数画图器
前端·javascript·html5