Vue3+vite+Ts+pinia—第八章 生命周期

8.1 生命周期概述

Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子。

  • 规律:

    生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。

  • Vue2的生命周期

    创建阶段:beforeCreatecreated

    挂载阶段:beforeMountmounted

    更新阶段:beforeUpdateupdated

    销毁阶段:beforeDestroydestroyed

  • Vue3的生命周期

    创建阶段:setup

    挂载阶段:onBeforeMountonMounted

    更新阶段:onBeforeUpdateonUpdated

    卸载阶段:onBeforeUnmountonUnmounted

  • 常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

8.2 创建阶段

1、Vue2(beforeCreate、created)

vue2在创建阶段有两个生命周期钩子,分别是beforeCreate和created,一个是创建前执行一个是创建完成执行。这两个钩子最大的区别体现在数据是否初始化完成,以下面为例:

javascript 复制代码
export default {
  data() {
    return {
      sum: 1
    }
  },
  beforeCreate() {
    console.log('创建前');
    console.log(this.sum);
  },
  created() {
    console.log('创建完毕');
    console.log(this.sum);
  }
}  

beforeCreate钩子执行时,datamethodswatchcomputed 等都还没有被初始化,所以你访问不到任何数据,也调用不了 watch

created钩子执行时, datamethodswatchcomputed 都已经初始化完成,所以你能访问到数据。

关于watch补充说明,虽然watch在created钩子已经初始化完成,但它的回调函数默认不会在此时执行,它只是被"准备好了",等待数据变化时才触发。默认情况(immediate: false):watch的回调函数不会在组件创建时执行,只有当你监听的数据发生变化时才会触发。设置了 immediate: true:watch的回调函数会在 created钩子之后立即执行一次。

2、Vue3(setup)

vue3没有细分创建前和创建完成这两个阶段,它统一在setup钩子里完成。它不仅具备了Vue2的beforeCreate和created这两个钩子的作用,而且它可以把所有业务逻辑都直接写在 setup 里,包括数据请求、事件监听、计算属性、副作用管理等。

关于setup有以下两种写法:

(1)普通setup()

xml 复制代码
<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    
    // ❌ 如果没有 return,模板就无法使用 count 和 increment
    return {
      count,
      increment
    }
  }
}
</script>

(2)<script setup>(不需要 return)

xml 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
// ✅ 无需 return,模板可以直接使用 count 和 increment
</script>

目前基本上都使用<script setup>这种语法糖,在这个模式下,所有顶层定义的变量和函数,都会被自动暴露给模板,完全不需要手动return。

当然,不管是vue2的beforeCreate/created还是vue3的setup,此时DOM元素还没有渲染出来,如果需要操作DOM,依然要在挂载阶段进行操作。

8.3 挂载阶段

挂载阶段分为挂载前和挂载结束两个阶段,分别对应vue2的beforeMount/mounted与vue3的onBeforeMount/onMounted,它们除了命名不同其它基本一致。

1、挂载前

在绝大多数日常业务开发中,这个钩子确实很少被用到,甚至可以说它对于90%的开发者来说,确实是"没什么用"的,因为在这个钩子能做的事,基本上都可以在created钩子做到。在这里对created与beforeMount做一个简单的区别:

created:datamethods都已经准备好,但页面模板还没有被编译成DOM元素 。此时你无法操作 this.$el,也无法通过 document.querySelector 找到页面上的任何元素(除非是静态硬编码的)。

beforeMount:模板编译已经完成,虚拟 DOM 节点已经生成 ,但还没有被挂载(渲染)到真实的 DOM 中 。此时 this.$el 仍然指向旧的挂载点(或空),真实 DOM 还是不可用。

使用场景:如果你能在虚拟 DOM 被渲染成真实 DOM 之前,对它做最后一次修改 ,就能避免不必要的 DOM 重绘。在编写 Vue 的自定义指令服务端渲染(SSR) 时,beforeMount 是区分客户端(浏览器)和服务器端执行逻辑的关键节点。因为 SSR 只会执行到 beforeMount 为止,后续的 mounted 只会在客户端执行。

2、挂载结束

mounted钩子可以说是 Vue 生命周期中最核心、最常用 的钩子之一。它的定位非常明确:当组件被挂载到真实 DOM 之后,你可以安全地操作 DOM 了。

用途分类 具体场景 为什么必须在 mounted 中做?
1. 操作 DOM 元素 获取元素宽高、滚动位置、聚焦输入框、动态修改样式等 只有挂载后,this.$elthis.$refs 才指向真实的 DOM 节点。
2. 初始化第三方 DOM 库 初始化 ECharts 图表、地图(如高德/百度)、swiper 轮播、富文本编辑器等 这些库都需要一个真实的 DOM 容器来渲染,必须等到挂载完成。
3. 启动定时器或轮询 定时刷新数据、倒计时、轮询接口状态等 通常与 DOM 展示或用户交互相关,挂载后启动更合理。
4. 依赖 DOM 的数据请求 需要获取页面元素尺寸后再请求数据(如自适应布局) 数据请求本身可在 created 做,但若依赖 DOM 信息,则必须在 mounted
5. 设置事件监听 监听 windowresizescroll 事件,或全局键盘事件等 需要确保组件已存在,避免在销毁时残留监听(需配合 beforeDestroy 清理)。

注意事项:

(1) 避免在 mounted 中修改响应式数据 :如果在 mounted 里直接修改了 data 中的某个值,会触发组件重新渲染,可能会引起性能问题或视觉闪烁。如果确实需要改,可以用 this.$nextTick 推迟到下一次 DOM 更新后执行。

(2) 子组件的挂载顺序 :父组件的 mounted先于 子组件的 mounted 执行。如果父组件需要等待子组件完全挂载后再操作,可以使用 this.$nextTick 或监听子组件的 mounted 事件。

(3) SSR(服务端渲染)中不会执行 :如果你的项目使用了 SSR(如 Nuxt),mounted 钩子只在浏览器端执行 ,服务器端不会调用。因此,任何浏览器特有的 API(如 windowdocument)操作都必须放在 mounted 中。

8.4 更新阶段

updated钩子在响应式数据发生变化,导致组件的虚拟 DOM 重新构建并完成对比(patch)之后 被调用。简单来说,就是:数据变了 → 组件重新渲染了 → DOM 更新完了 → 这时updated钩子执行

1、主要用途

updated的核心价值在于:当你需要在 DOM 更新完成后执行某些操作时,它是一个精确的时机。典型场景包括:

场景 说明
1. 操作更新后的 DOM 例如,数据变化后,需要获取某个元素的新尺寸、新滚动位置,或对 DOM 做额外的操作。
2. 更新第三方 DOM 库 例如,ECharts 图表数据变化后,需要重新设置 setOption;地图需要重新调整视图等。
3. 触发子组件或外部依赖的更新 例如,子组件内部依赖父组件传入的 prop,当 prop 变化后,需要在 updated 中做后续处理。
4. 调试或日志记录 在开发环境下,可以在 updated 中打印当前状态,观察组件渲染情况。

2、实战代码示例

xml 复制代码
<template>
  <div>
    <ul ref="listRef">
      <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    </ul>
    <p>列表长度: {{ list.length }}</p>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 1, name: 'Apple' },
        { id: 2, name: 'Banana' }
      ]
    }
  },
  methods: {
    addItem() {
      this.list.push({ id: Date.now(), name: 'New Item' })
    }
  },
  updated() {
    // ✅ 1. 获取更新后的 DOM 信息(比如列表高度变化)
    const height = this.$refs.listRef.offsetHeight
    console.log('列表更新后高度:', height)

    // ✅ 2. 如果列表滚动到底部,自动滚动到最新项(消息列表常见场景)
    const container = this.$refs.listRef
    container.scrollTop = container.scrollHeight

    // ✅ 3. 触发外部依赖(比如通知父组件或更新第三方库)
    // this.updateExternalChart()
    
    // ⚠️ 4. 注意:不要在这里修改响应式数据,会导致无限循环
    // this.list = [...] // ❌ 千万不要这样做!
  }
}
</script>

3、注意事项

不要在 updated 中修改响应式数据!

如果在updated中修改了data中的某个值,会立即触发新一轮的渲染,然后再次进入updated,从而形成死循环,最终导致浏览器崩溃。

javascript 复制代码
updated() {
  // ❌ 绝对禁止:修改响应式数据
  this.count++  // 会触发重新渲染 → 再次进入 updated → 无限循环
}

如果确实需要在 DOM 更新后修改数据,可以使用 this.$nextTick 来推迟修改,但在updated中直接做依然风险很高,一般不推荐。

4、updated vs watch

对比维度 watch updated
触发粒度 精确到某个具体数据(监听谁就触发谁) 粗粒度(任何数据变化都会触发)
执行时机 数据变化时立即执行(DOM 可能还没更新) DOM 更新完成后执行
是否包含 DOM 操作 可以,但需要配合 this.$nextTick 才能拿到新 DOM 直接可以操作最新 DOM
适用场景 监听特定数据变化,执行特定逻辑 需要等待 DOM 更新后做通用处理
  • 如果你只关心某个具体数据的变化 → 用 watch

  • 如果你关心 DOM 是否更新完毕,且不关心是哪个数据触发的 → 用 updated

  • 如果你需要同时监听数据变化并在 DOM 更新后做操作 → 用 watch + this.$nextTick

8.5 销毁阶段

1、清理工作

销毁阶段最重要的任务,用一个词概括就是:清理

清理类型 具体内容 为什么要清理?
清除定时器 clearInterval() / clearTimeout() 定时器会持续占用内存和 CPU,即使组件已销毁,它仍在后台运行。
移除全局事件监听 removeEventListener() 挂载在 windowdocumentEventBus 上的监听器不会自动销毁,会一直存在,导致内存泄漏或逻辑错乱。
销毁第三方库实例 ECharts 的 dispose()、地图的 destroy()swiperdestroy() 第三方库通常会在 DOM 上绑定大量事件或创建复杂的对象,需要手动释放资源。
取消未完成的请求 取消 axios 请求(CancelToken)或 fetch 请求(AbortController 组件销毁后,返回的数据已经无用,但请求本身仍会占用网络和内存资源。
断开 WebSocket / 实时连接 socket.close() 保持无效连接会浪费服务器资源和网络带宽。
取消观察 / 订阅 取消对 watch 的监听(unwatch)、取消对 EventBusstore 的订阅 防止组件销毁后,仍然响应全局事件或状态变化,导致报错或逻辑错乱。

如果忘记清理上述资源,有可能会发生以下情况。

  1. 定时器泄漏:用户从页面 A 跳到页面 B,但页面 A 的定时器还在运行,每隔几秒还在请求数据、更新已经不存在的 DOM,CPU 和内存持续消耗。

  2. 事件监听泄漏window.resize 事件仍然在调用一个已销毁组件的方法,导致报错 "Cannot read property of undefined"。

  3. 第三方库泄漏:ECharts 实例没有被销毁,它持有的 DOM 引用和事件监听会阻止垃圾回收,导致内存不断攀升。

  4. 请求泄漏:组件销毁后,异步请求返回的数据试图更新一个已经不存在的组件状态,导致控制台报错。

2、beforeDestroy vs destroyed

实际开发中,你几乎只需要关心 beforeDestroy(Vue 3 中为 onBeforeUnmount)。destroyed 很少被用到,因为此时组件已经"死亡",能做的事情非常有限。

对比维度 beforeDestroy destroyed
触发时机 销毁前 销毁后
data / methods 是否可用 ✅ 完全可用 ❌ 已不可用
DOM 是否存在 ✅ 存在 ❌ 已移除
适合做什么 99% 的清理工作(定时器、事件、第三方库) 极少数收尾工作(通常不需要)
相关推荐
这是个栗子2 小时前
【Vue代码分析】 import.meta.env.DEV:现代前端工程中的环境变量
前端·javascript·vue.js·环境变量
乌夷5 小时前
Vue.js 3 的设计思路
前端·javascript·vue.js
前端之虎陈随易15 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
格子软件17 小时前
2026年GEO贴牌代理:分布式多级分账状态机源码深度解构
java·vue.js·分布式·vue·geo
之歆18 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
奶油mm1 天前
公司技术债堆积如山,我一人之力用 Vue3 偷换了整个前端架构
前端·vue.js
格子软件1 天前
2026年分布式GEO代理流量调度:源码级状态机防重挂实战
java·vue.js·人工智能·spring boot·分布式·vue
前端炒粉1 天前
个人简历面经总结二
前端·网络·vue.js·react.js·面试
格子软件1 天前
2026年分布式GEO代理架构:多租户动态数据源隔离与流控源码解构
java·vue.js·人工智能·分布式·架构·vue·geo