Vue 生命周期详解

一、什么是生命周期

1.1 定义

Vue 生命周期 是指一个组件从创建挂载更新卸载 的整个过程。在这个过程中,Vue 会在每个关键节点自动调用一些特殊函数 ,这些函数就叫生命周期钩子(Lifecycle Hooks)。

关键点:钩子函数是 Vue 主动叫你的,不是你叫它。你只需要把代码写进对应的钩子里,Vue 会在合适的时机执行。

1.2 生活类比

把组件想象成一个"员工"的一生:

  • 出生 → 接入职手续(created)
  • 上岗 → 拿到工牌、坐到工位(mounted)
  • 工作变动 → 调岗、改工资(updated)
  • 离职 → 交还工牌、清理工位(unmounted)

每个节点公司都会通知你办手续,你不用自己盯,Vue 就是这个"公司"。

1.3 为什么要懂生命周期

因为不同阶段能干的事不一样:

  • 想拿数据 → 得在 created 之后(数据才成型)
  • 想操作 DOM → 得在 mounted 之后(节点才上树)
  • 想清理定时器 → 得在 beforeUnmount(趁还没走)
  • 想监听数据变化 → 得在 updated 之前(避免死循环)

写错时机,要么拿不到东西,要么报错,要么内存泄漏。


二、生命周期的四个阶段

Vue 组件的一生可以分成四个阶段,每个阶段有一个"前"一个"后"两个钩子。

阶段 Vue 3 钩子 Vue 2 钩子 干什么
创建 beforeCreate / created beforeCreate / created 初始化数据、观测
挂载 beforeMount / mounted beforeMount / mounted 编译模板、挂载 DOM
更新 beforeUpdate / updated beforeUpdate / updated 数据变化时重新渲染
卸载 beforeUnmount / unmounted beforeDestroy / destroyed 销毁实例、清理收尾

下面逐个阶段讲。本文以 Vue 3 为主,Vue 2 的差异在第十节统一对比。


三、创建阶段:beforeCreate & created

3.1 beforeCreate

实例刚被创建,但是数据观测(data)和事件配置都还没开始 。这时候 datacomputedmethods 全都访问不到。

javascript 复制代码
export default {
    data() {
        return { msg: "hello" };
    },
    beforeCreate() {
        console.log(this.msg);  // undefined  ← data 还没挂载
        console.log(this.sayHi); // undefined  ← methods 也没好
    },
    methods: {
        sayHi() { console.log("hi"); }
    }
};

用途:几乎不用。想做"最早的全局配置"才碰它(比如改全局配置项),日常开发基本碰不到。

3.2 created

实例创建完成,data、computed、methods 都已经就位 ,但还没开始编译模板,DOM 还不存在

javascript 复制代码
export default {
    data() {
        return { userList: [] };
    },
    async created() {
        // 数据已经能用,可以发请求
        const res = await fetch("/api/users");
        this.userList = await res.json();  // ← 能正常赋值
        console.log(this.$el);  // undefined  ← 还没挂载,拿不到根节点
    }
};

用途 :发请求拿数据的最佳时机之一。因为这时 data 已经能用,且比 mounted 更早,数据可以更早返回、更早渲染。SSR 场景下也只有 created 能用(mounted 在服务端不执行)。

3.3 注解

1.beforeCreate 和 created 的分界线

分界线就是"有没有完成 data 的响应式初始化 "。beforeCreate 之前 data 还是 undefined,之后(created)就能 this.msg 访问了。这个分界由 Vue 内部的 initState() 完成,它会依次处理 props、methods、data、computed、watch。

2.为什么 created 不能操作 DOM

因为模板还没编译,DOM 还没生成。this.$el 此时是 undefined,document.querySelector 也找不到组件内部的元素。要操作 DOM 必须等 mounted。


四、挂载阶段:beforeMount & mounted

4.1 beforeMount

模板已经编译成渲染函数,马上要把虚拟 DOM 转成真实 DOM 挂到页面上,但还没挂,页面还是旧的。

javascript 复制代码
export default {
    data() { return { count: 0 }; },
    beforeMount() {
        console.log(document.getElementById("app").innerHTML);
        // 还是旧内容  ← 还没替换
        console.log(this.$el);  // undefined(Vue3) / 即将挂载的元素(Vue2)
    }
};

用途:基本不用。极少情况下需要在挂载前做最后一道处理,比如改一下要渲染的数据(但通常直接改 data 就够了)。

4.2 mounted

真实 DOM 已经挂到页面上 ,this.$eldocument.querySelector 都能用了。这是组件生命周期里第一个能操作 DOM 的时机

javascript 复制代码
export default {
    data() { return { chart: null }; },
    mounted() {
        // DOM 已经存在,可以初始化第三方库
        const canvas = document.getElementById("myCanvas");
        this.chart = new Chart(canvas, { /* 配置 */ });

        // 可以拿到实际尺寸了
        const rect = canvas.getBoundingClientRect();
        console.log(rect.width);  // 真实像素值  ← mounted 才测得准
    }
};

用途:

  • 初始化依赖 DOM 的第三方库(ECharts、Swiper、CodeMirror 等)
  • 监听 window/document 事件(resize、scroll、keydown)
  • 启动定时器、WebSocket 连接
  • 拿元素尺寸(getBoundingClientRect)

4.3 注解

1.mounted 不代表所有子组件都挂载完成

在 Vue 3 中,父组件的 mounted 保证自身 DOM 已挂载 ,但不保证所有子组件都已挂载完成 (子组件可能是异步的)。如果需要等整个树都准备好,可以用 this.$nextTick 或在子组件挂载后用 emits 通知父组件。

2.mounted 只执行一次

和 created 一样,正常情况下 mounted 只触发一次。后续数据变化只会触发更新阶段的钩子,不会再走 created/mounted。除非组件被 keep-alive 缓存后再次激活,那走的是 activated 而不是 mounted。


五、更新阶段:beforeUpdate & updated

5.1 beforeUpdate

数据变了,但 DOM 还没重新渲染 。这时 this.xxx 是新值,但页面上的还是旧值。

javascript 复制代码
export default {
    data() { return { count: 0 }; },
    beforeUpdate() {
        console.log(this.count);  // 新值,比如 1  ← 数据已经更新
        console.log(this.$el.textContent);  // 旧值 0  ← DOM 还没同步
    }
};

用途 :在 DOM 重新渲染前最后改一次状态的好时机。比如某个数据变了,你需要在更新前同步更新另一个依赖它的数据,避免触发两次重渲染。

5.2 updated

数据变了,DOM 已经重新渲染完毕。这时数据和页面是同步的。

javascript 复制代码
export default {
    data() { return { list: [] }; },
    updated() {
        // 列表更新后,DOM 也更新了,可以拿到新增的元素
        const lastItem = document.querySelector(".item:last-child");
        console.log(lastItem);  // 新渲染的节点  ← 这时才能拿到
    }
};

用途:

  • 拿数据更新后的最新 DOM(比如列表渲染完后做滚动定位)
  • 依赖 DOM 尺寸的二次计算(如瀑布流重排)

5.3 注解

1.updated 里千万别直接改数据,会死循环

在 updated 里改 data,会让数据又变,又触发 update,又执行 updated......形成无限递归,栈溢出或卡死。如果一定要改,必须加条件:

javascript 复制代码
updated() {
    if (this.needFix) {
        this.needFix = false;  // ← 改完立刻把自己关掉
        this.someValue = "fixed";
    }
}

更好的做法是用 watch + this.$nextTick,而不是在 updated 里改。

2.updated 是组件任意数据变化都会触发

不是只有你看的那个数据变才触发,组件内任何响应式数据变了都会触发 updated。所以 updated 会被频繁调用,在里面干重活代价很大。


六、卸载阶段:beforeUnmount & unmounted

6.1 beforeUnmount

组件马上要被销毁,但此刻实例还完全可用,data、methods、DOM 都还在。

javascript 复制代码
export default {
    data() { return { timer: null }; },
    beforeUnmount() {
        // 还能访问 this,适合做清理
        clearInterval(this.timer);
        window.removeEventListener("resize", this.handleResize);
        this.socket?.close();
    }
};

用途 :清理工作的主战场。在这里取消定时器、移除事件监听、关闭 WebSocket、取消 axios 请求、销毁第三方库实例。

6.2 unmounted

组件已经销毁,所有指令解绑、事件监听器移除、子实例卸载完毕。这时再操作 this 已经没意义了

javascript 复制代码
export default {
    unmounted() {
        console.log("走好");  ← 最后道别,通常不再做重活
    }
};

用途:基本不用。该清理的活儿在 beforeUnmount 就干完了,unmounted 更像是个"墓志铭"钩子。

6.3 注解

1.为什么清理要放 beforeUnmount 而不是 unmounted

因为 beforeUnmount 时 this 还完整,定时器 id、监听器引用都还能拿到。到 unmounted 时很多内部引用可能已经被 Vue 置空,再清理就麻烦了。记住一个口诀:清理在 before,告别在 after

2.路由切换不一定触发 unmount

如果组件被 <keep-alive> 包裹,路由切走时调用的是 deactivated 而不是 unmounted,组件被缓存,下次回来调用 activated。只有真正离开 keep-alive 缓存范围才会 unmount。


七、特殊钩子

除了四大阶段,Vue 还提供了一些特殊场景的钩子。

7.1 keep-alive 专属:activated & deactivated

<keep-alive> 包裹的组件被缓存,再次激活时不会重新走 created/mounted,而是触发这两个钩子。

javascript 复制代码
export default {
    activated() {
        console.log("从缓存中唤醒");  ← 适合恢复定时器、开始轮询
    },
    deactivated() {
        console.log("被缓存(休眠)");  ← 适合暂停定时器、停止轮询
    }
};
状态 普通组件 keep-alive 组件
首次进入 created → mounted created → mounted → activated
离开 beforeUnmount → unmounted deactivated
再次进入 created → mounted activated(不走 created)
真正销毁 - beforeUnmount → unmounted

7.2 错误捕获:errorCaptured

后代组件抛出错误时触发,离错误最近的父级先收到,然后错误会沿父链向上抛。

javascript 复制代码
export default {
    errorCaptured(err, instance, info) {
        console.log("子组件炸了:", err.message);
        console.log("在哪个组件:", instance);
        console.log("错误来源:", info);
        return false;  // ← 返回 false 阻止错误继续向上传播
    }
};

用途:做组件级错误兜底,比如显示一个 fallback UI,而不是整个页面崩掉。

7.3 调试专用:renderTracked & renderTriggered(Vue 3)

只在开发环境生效,用来调试哪个响应式数据触发了一次重新渲染

javascript 复制代码
export default {
    renderTriggered(event) {
        console.log("谁触发了我:", event);  ← { effect, target, key, type }
    }
};

线上不要用,生产环境会被自动移除。


八、父子组件生命周期的执行顺序

这是面试和实战都很常考的点。记住一句话:创建是父先,挂载是子先

8.1 挂载阶段

复制代码
父 beforeCreate
父 created
父 beforeMount
    子 beforeCreate
    子 created
    子 beforeMount
    子 mounted
父 mounted   ← 父等子挂完才 mounted

为什么这样:父组件挂载前需要先把子组件渲染好,所以父 beforeMount 之后会去走子的流程,等所有子组件都 mounted 了,父才 mounted。

8.2 更新阶段

复制代码
父 beforeUpdate
    子 beforeUpdate
    子 updated
父 updated

数据从父流向子,所以父先知道要更新,再通知子,子更新完父才收尾。

8.3 卸载阶段

复制代码
父 beforeUnmount
    子 beforeUnmount
    子 unmounted
父 unmounted

父要走了,先把子送走,自己再走。

8.4 代码验证

javascript 复制代码
// 父组件
export default {
    beforeMount() { console.log("父 beforeMount"); },
    mounted() { console.log("父 mounted"); }
}

// 子组件
export default {
    beforeMount() { console.log("  子 beforeMount"); },
    mounted() { console.log("  子 mounted"); }
}

// 控制台输出:
// 父 beforeMount
//   子 beforeMount
//   子 mounted
// 父 mounted

九、常见使用场景速查

场景 推荐钩子 原因
发请求拿数据 created 数据已就位,比 mounted 更早
操作 DOM / 初始化第三方库 mounted DOM 才存在
监听 window 事件、启动定时器 mounted 需要在 beforeUnmount 配套清理
清理定时器 / 移除监听 / 关连接 beforeUnmount this 还能用,引用还在
数据变化后做 DOM 二次操作 updated / watch+nextTick DOM 已同步
keep-alive 缓存组件恢复轮询 activated / deactivated 不走 created/mounted
全局错误兜底 errorCaptured 捕获后代组件错误
调试响应式触发来源 renderTriggered 仅开发环境

相关推荐
江米小枣tonylua2 小时前
TypeScript 全面の拥抱 !Prisma 8 + Electron 升级实战
前端
闪耀之光M782 小时前
npm命令解析
前端
咸鱼老弟2 小时前
AI Agent 的"自主性悖论"——为什么给它的自由度越大,越要配一套更硬的护栏
前端·人工智能
默_笙3 小时前
💫 闭包是个背包:拆解小米前端面试题里的三道"闭包陷阱"
前端·javascript·面试
天若有情6733 小时前
粒子星空背景单页HTML模板|炫酷动态星空特效网页(纯前端)
前端·html·css动画·网页特效·粒子星空·静态网页模版
工业涂料百问3 小时前
【趋势前沿】系列(三)工业涂料国产替代的三个台阶:航空、海工、电子的突破路径与认证壁垒
前端
前端snow3 小时前
ai agent --- agentic RAG
前端
gyratesky3 小时前
记录一种很新的大屏开发方式
前端·数据可视化