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

8.1 生命周期概述

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

  • 规律:

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

  • Vue2的生命周期

    创建阶段:beforeCreate、created

    挂载阶段:beforeMount、mounted

    更新阶段:beforeUpdate、updated

    销毁阶段:beforeDestroy、destroyed

  • Vue3的生命周期

    创建阶段:setup

    挂载阶段:onBeforeMount、onMounted

    更新阶段:onBeforeUpdate、onUpdated

    卸载阶段:onBeforeUnmount、onUnmounted

  • 常用的钩子: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钩子执行时,data、methods、watch、computed 等都还没有被初始化,所以你访问不到任何数据,也调用不了 watch。

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

关于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:data、methods都已经准备好,但页面模板还没有被编译成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.$el 和 this.$refs 才指向真实的 DOM 节点。
2. 初始化第三方 DOM 库 初始化 ECharts 图表、地图(如高德/百度)、swiper 轮播、富文本编辑器等 这些库都需要一个真实的 DOM 容器来渲染,必须等到挂载完成。
3. 启动定时器或轮询 定时刷新数据、倒计时、轮询接口状态等 通常与 DOM 展示或用户交互相关,挂载后启动更合理。
4. 依赖 DOM 的数据请求 需要获取页面元素尺寸后再请求数据(如自适应布局) 数据请求本身可在 created 做,但若依赖 DOM 信息,则必须在 mounted。
5. 设置事件监听 监听 window 的 resize、scroll 事件,或全局键盘事件等 需要确保组件已存在,避免在销毁时残留监听(需配合 beforeDestroy 清理)。

注意事项:

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

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

(3) SSR(服务端渲染)中不会执行 :如果你的项目使用了 SSR(如 Nuxt),mounted 钩子只在浏览器端执行 ,服务器端不会调用。因此,任何浏览器特有的 API(如 window、document)操作都必须放在 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() 挂载在 window、document 或 EventBus 上的监听器不会自动销毁,会一直存在,导致内存泄漏或逻辑错乱。
销毁第三方库实例 ECharts 的 dispose()、地图的 destroy()、swiper 的 destroy() 等 第三方库通常会在 DOM 上绑定大量事件或创建复杂的对象,需要手动释放资源。
取消未完成的请求 取消 axios 请求(CancelToken)或 fetch 请求(AbortController) 组件销毁后,返回的数据已经无用,但请求本身仍会占用网络和内存资源。
断开 WebSocket / 实时连接 socket.close() 保持无效连接会浪费服务器资源和网络带宽。
取消观察 / 订阅 取消对 watch 的监听(unwatch)、取消对 EventBus 或 store 的订阅 防止组件销毁后,仍然响应全局事件或状态变化,导致报错或逻辑错乱。

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

  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% 的清理工作(定时器、事件、第三方库) 极少数收尾工作(通常不需要)
相关推荐
A黄俊辉A4 天前
uniapp webview中实现 app和内嵌的H5双向通信
vue.js·json
honkun64 天前
vue 表格组件 vxe-table 配置 ajax 请求自动加载数据与表单查询
vue.js·vxe-table
kybs19914 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
雪芽蓝域zzs4 天前
第26章:ECharts 自定义主题 + 页面一键切换主题(深色 / 浅色两套)
vue.js·echars
雪芽蓝域zzs4 天前
第22章:ECharts 图表联动(多图表交互联动)
vue.js·echars
daols884 天前
vue 表格组件 vxe-table 实现虚拟滚动与无需滚动加载
vue.js·vxe-table
雪芽蓝域zzs4 天前
第18章:ECharts 折线图 + 标记点、标记线,自定义 tooltip
vue.js·echars
雪芽蓝域zzs4 天前
第24章:ECharts 地图组件(基础行政区地图,若依 Vue3)
vue.js·echars
计算机毕设定制辅导-无忧学长4 天前
《基于Vue的流浪动物救助中心管理系统的设计与实现》
java·vue.js·spring boot·流浪动物救助中心管理系统
雪芽蓝域zzs4 天前
第25章:地图下钻交互(点击省份,切换到对应省份城市地图)
vue.js·echars