跳转到内容

Vue3核心知识点


vite 是新一代前端构建工具,官网地址:https://vitejs.cnvite的优势如下:

  • 轻量快速的热重载(HMR),能实现极速的服务启动。
  • TypeScriptJSXCSS 等支持开箱即用。
  • 真正的按需编译,不再等待整个应用编译完成。
  • webpack构建 与 vite构建对比图如下: webpack构建 vite构建 具体操作如下(官方文档
1. 创建命令
npm create vue@latest
---
2. 配置项目名称
√ Project name: 项目名称
---
3. 是否添加TypeScript支持
√ Add TypeScript? Yes
---
3. 是否添加JSX支持
√ Add JSX Support? No
---
4. 是否添加路由环境
√ Add Vue Router for Single Page Application development? No
---
5. 是否添加pinia环境
√ Add Pinia for state management? No
---
6. 是否添加单元测试
√ Add Vitest for Unit Testing? No
7. 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
---
8. 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
---
9. 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No

总结:

  • Vite 项目中,index.html 是项目的入口文件,在项目最外层。
  • 加载index.html后,Vite 解析 <script type="module" src="xxx"> 指向的JavaScript
  • Vue3**中是通过 **createApp 函数创建一个应用实例。
import './assets/main.css'
import { createApp } from 'vue' // 创建应用 (花盆)
import App from './App.vue' // 组件(相当于花的根)
createApp(App).mount('#app')
  • createApp(App) 会创建一个 Vue 应用,App 是项目的根组件,也就是整个 Vue 应用的起点。
  • App 通常是一个 Vue 组件,包含了数据、模板、方法等。`
  • .mount('#app') 这一步把 Vue 应用挂载到 index.html 文件中的指定 DOM 元素上,#appid 选择器,表示挂载的位置。

Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

而Composition API就是把相关逻辑写在一起,而不是按 API 类型拆分。

setupVue3中一个新的配置项,值是一个函数,它是Composition API 表演的舞台,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup中。

特点如下:

  • setup 函数返回的对象中的内容,可直接在模板中使用;

  • setup 中访问 thisundefined,不可使用;

  • setup 函数会在 beforeCreate 之前调用,它是“领先”所有钩子执行的。

<template>
<div class="person">
<!-- 模板中可以直接使用 setup 返回的数据 -->
<h2>姓名{{ name }}</h2>
<h2>年龄{{ age }}</h2>
<!-- 事件绑定调用 setup 中返回的方法 -->
<button @click="changeName">修改名字</button>
<button @click="changeAge">年龄+1</button>
<button @click="showTel">点我查看联系方式</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person',
/**
* setup 是 Vue 3 Composition API 的入口函数
* 在组件创建之前执行
*/
setup() {
// 这里定义的数据,原本在 Vue2 中写在 data() 里
// ⚠ 注意:此时的 name、age、tel 都是普通变量,不是响应式数据。
let name = '张三'
let age = 18
let tel = '13888888888'
// 这里的方法,原本在 Vue2 中写在 methods 里
function changeName() {
name = 'zhang-san'
}
function changeAge() {
age += 1
}
function showTel() {
alert(tel)
}
// 暴露给模板使用
return {
name,
age,
tel,
changeName,
changeAge,
showTel
}
}
}
</script>

Vue2 风格的配置项(datamethodscomputed 等),可以访问 setup 中返回的属性和方法。

示例:

export default {
setup() {
const msg = 'hello'
function sayHi() {
console.log('hi')
}
return { msg, sayHi }
},
mounted() {
// ✅ 可以访问 setup 返回的内容
console.log(this.msg)
this.sayHi()
}
}

原因:setup 返回的数据会被 合并到组件实例上,供 Options API 使用。

但在 setup 中,访问不到 datamethodscomputed 等 Options API 配置。

示例:

export default {
data() {
return {
count: 10
}
},
setup() {
console.log(this.count) // ❌ undefined
}
}
  • setup 执行时:

    • 组件实例 还没有创建

    • thisundefined

  • 所以:

    • 访问不到 data

    • 访问不到 methods

    • 访问不到 computed

    • 访问不到 this

<script setup> 是 Vue 3 为 Composition API 提供的 编译期语法糖,用于简化 setup() 的写法,不需要手动 return

  • 传统写法
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const add = () => {
count.value++
}
return {
count,
add
}
}
}
</script>
  • setup 语法糖写法
<script setup>
import { ref } from 'vue'
const count = ref(0)
const add = () => {
count.value++
}
</script>

在 Vue 3 中使用:

<script setup>
  • 无法直接声明组件 name

原始写法:

<script setup>
</script>
<script>
export default {
name: 'Person'
}
</script>

Vite插件扩展:

  • 安装插件
Terminal window
npm i vite-plugin-vue-setup-extend -D
  • vite.config.ts 中注册插件
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [VueSetupExtend()]
})
  • <script setup> 中直接声明 name
<script setup lang="ts" name="Person">
</script>
  • 插件会在 编译阶段 自动生成
export default {
name: 'Person'
}

defineOptions:

<script setup lang="ts">
defineOptions({
name: 'Person'
})
</script>

Vue 3.3 开始,官方提供:defineOptions

2.3.1. ref创建基本类型的响应式数据

Section titled “2.3.1. ref创建基本类型的响应式数据”

ref是Vue 3中用于创建响应式数据 的 API,用来让 基本类型数据number / string / boolean 等)也能拥有 响应式能力

  1. 创建响应式数据
import { ref } from 'vue'
const count = ref(0)
  1. 读取和修改值
count.value // 读取
count.value++ // 修改

.value 是必须的;

  1. 模板中使用(自动解包)
<p>{{ count }}</p>
<button @click="count++">+</button>

模板中 不需要 .value

2.3.2. reactive创建对象类型的响应式数据

Section titled “2.3.2. reactive创建对象类型的响应式数据”

reactive 用于 创建响应式对象,通过 Proxy 对对象进行代理,实现数据变化自动更新视图。

专门用于对象 / 数组 / Map / Set 等引用类型

  1. 创建响应式对象
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Alice'
}
})
  1. 读取与修改
state.count++
state.user.name = 'Bob'

不需要 .value,直接操作属性。

reactive 具有深度响应式的特点,当对象中存在嵌套结构时,即使修改的是深层属性(如 state.user.name = 'Tom'),依然可以触发视图更新。需要注意的是,reactive 只能接收对象类型的数据,不能用于基本类型(如 reactive(1)reactive('hello') 均无效),其适用范围包括 ObjectArrayMapSet 等引用类型。reactive 基于 Proxy 实现,这是 Vue 3 相比 Vue 2 的核心优势之一,它可以监听属性的新增和删除,性能更好,也不再需要使用 Vue.set

同时,reactive 也存在一定的局限性:不能直接替换整个响应式对象,例如 state = { count: 10 } 会导致响应式失效,正确的做法应是修改对象内部属性(如 state.count = 10)。此外,对 reactive 对象进行解构会丢失响应式,例如 const { count } = state 是错误的,解决方案是配合 toRefs 使用:const { count } = toRefs(state),以保持属性的响应式能力。

2.3.3. ref创建对象类型的响应式数据

Section titled “2.3.3. ref创建对象类型的响应式数据”

在Vue 3中,ref不仅可以用于基本类型数据,也可以用于对象类型数据。当使用 ref 包裹一个对象时,Vue内部会自动将该对象转换为深度响应式对象(本质上等价于对对象使用 reactive),但最外层仍然是一个ref,因此在 JavaScript 中访问或修改时必须通过.value

import { ref } from 'vue'
const user = ref({
name: 'Tom',
age: 18
})

访问和修改对象属性时,需要先通过.value 取出对象:

user.value.age++
user.value.name = 'Jerry'

由于对象内部已经是响应式的,修改其任意属性都会触发视图更新。在模板中使用时,Vue 会对 ref 进行 自动解包(unref),因此可以直接写成:

<p>{{ user.name }} - {{ user.age }}</p>

无需使用 .value

需要注意的是,虽然 ref 包裹对象在使用上类似 reactive,但二者仍有区别:ref 更适合用于需要整体替换对象需要解构仍保持响应式的场景,而 reactive 更适合表示结构稳定的状态对象。

2.3.4. refreactive对象整体替换与响应式

Section titled “2.3.4. ref与reactive对象整体替换与响应式”
let state = reactive({ user: { name: 'Tom', age: 18 } })
state = { user: { name: 'Jerry', age: 20 } } // ❌ 失去响应式
state = reactive({ user: { name: 'Jerry', age: 20 } }) // ❌ 依然失去响应式

原因说明:

  • reactive 返回的是一个 Proxy 对象

  • Vue 的模板和依赖追踪 绑定在第一次创建的 Proxy 上

  • 后续只是修改了 JS 变量的指向;

  • Vue 不会重新建立依赖关系

即:响应式系统仍然监听的是“旧的 Proxy”

2.3.4.1 reactive的正确整体替换方式
Section titled “2.3.4.1 reactive的正确整体替换方式”
Object.assign(state, {
user: {
name: 'Jerry',
age: 20
}
})
  • 不改变对象引用

  • 只修改对象内部属性

  • 响应式能力不丢失

state.value = {
user: {
name: 'Jerry',
age: 20
}
}

通过 .value 重新赋值整个对象

  • ref 的响应式绑定在 .value

  • .value 重新赋一个 新对象

    • 不会丢失响应式

    • Vue 会对新对象重新做响应式处理

  • reactive 不同,ref 天然支持对象整体替换

toReftoRefs 是 Vue 3 Composition API 中用于处理响应式对象属性的两个工具函数,主要用于在保持响应性的同时,将响应式对象的属性“解构”出来。

作用:为响应式对象的某个属性创建一个 ref 对象,该 ref 与其源属性保持双向同步(即修改 ref 的值会更新原对象,反之亦然)。

语法

const refForKey = toRef(object, 'key')

使用场景:当需要将响应式对象的单个属性作为 ref 传递给组合函数(composables)或 props,同时希望保持其响应性连接。

import { reactive, toRef } from 'vue'
const state = reactive({ count: 0 })
const countRef = toRef(state, 'count')
// 修改 countRef 会影响 state.count
countRef.value++
console.log(state.count) // 1
// 修改 state.count 也会影响 countRef
state.count++
console.log(countRef.value) // 2

作用:将一个响应式对象的所有属性转换为ref对象,返回一个普通对象,其每个属性都是对应的ref

语法

const refs = toRefs(reactiveObject)

使用场景:常用于在 setup() 中解构响应式对象,避免因解构而丢失响应性。

import { reactive, toRefs } from 'vue'
const state = reactive({
name: 'Alice',
age: 25
})
// 错误解构(会失去响应性):
// const { name, age } = state ❌
// 正确方式:
const { name, age } = toRefs(state)
// 现在 name 和 age 都是 ref
name.value = 'Bob'
console.log(state.name) // 'Bob'

典型用途:在组合函数中返回多个响应式状态,并允许调用者通过解构使用:

function useUser() {
const user = reactive({ name: '', email: '' })
return toRefs(user)
}
// 使用
const { name, email } = useUser()

computed 是 Vue 3 Composition API 中用于创建计算属性(computed properties)的核心函数。它基于响应式依赖进行缓存的、自动更新的派生状态,非常适合用于从现有响应式数据中派生出新值。

computed 内部会自动追踪其用到的响应式数据(如 refreactive 对象的属性),只要依赖没有变化,多次访问 computed 的值不会重复执行计算函数,而是直接返回缓存结果。当依赖发生变化时,下一次访问 computed 值会重新计算。

import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 创建一个只读的计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
console.log(fullName.value) // "John Doe"
// 修改依赖
firstName.value = 'Jane'
console.log(fullName.value) // "Jane Doe"(自动更新)

注意:fullName 是一个 ref 对象,必须通过 .value 访问其值。

有时希望反向设置计算属性,这时可以提供一个 gettersetter

const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
const [first, last] = newValue.split(' ')
firstName.value = first
lastName.value = last
}
})
// 使用
fullName.value = 'Alice Smith'
console.log(firstName.value) // 'Alice'
console.log(lastName.value) // 'Smith'

setter 中通常需要将新值“拆解”回原始响应式数据。

特性computedmethod(函数)
缓存✅ 有缓存,依赖不变则不重算❌ 每次调用都执行
响应式✅ 自动追踪依赖并更新❌ 需手动调用
模板中使用无需加 ()需要加 ()

watch 是 Vue 3 中用于监听响应式数据变化并在变化发生时执行指定逻辑(副作用) 的 API,本质上就是“当数据变了,就做点事情”。它常用于数据变化后触发异步请求、状态联动、日志记录或与外部系统交互等场景。在 Vue 3 中,watch 只能监视四类数据来源:ref 定义的数据、由 reactive 定义的数据、返回一个值的函数(getter)、以及由以上任意内容组成的数组;当这些被监视的值发生变化时,watch 会调用回调函数并提供新值和旧值。

2.4.1. 监视ref定义的基本数据类型

Section titled “2.4.1. 监视ref定义的基本数据类型”

监视由 ref 定义的基本数据类型是 watch 最基础、最常见的用法,主要用于在数据变化时执行相应的逻辑。

import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log('count changed:', oldValue, '', newValue)
})
  • 第一个参数:要监视的 ref

  • 第二个参数:回调函数

    • newValue:新值(自动解包)

    • oldValue:旧值

默认行为:

count.value++

值发生变化时才会触发,初始化不会触发。

立即执行(immediate):

watch(
count,
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{ immediate: true }
)

watch 在初始化时就执行一次,有点而像do...while

  • 第一次执行时:

    • newVal = 当前值

    • oldVal = undefined

2.4.2. 监视ref定义的对象数据类型

Section titled “2.4.2. 监视ref定义的对象数据类型”

在 Vue 3 中,ref 定义的是“对象类型数据”时,watch 的行为和监视基本数据类型是有区别的监视 ref 定义的对象时,默认是浅监听,只在对象整体被替换时触发;若要监听内部属性变化,需要使用 deep: true,或更推荐使用 getter 精准监听具体属性。

import { ref } from 'vue'
const user = ref({
name: 'Tom',
age: 18
})

此时:

  • user 是一个 ref;

  • user.value 是一个 对象;

  • 对象内部属性是响应式的。

2.4.2.1. 直接监视 ref 对象(默认行为)
Section titled “2.4.2.1. 直接监视 ref 对象(默认行为)”
import { watch } from 'vue'
watch(user, (newVal, oldVal) => {
console.log(newVal, oldVal)
})

如果希望对象内部属性变化也能被监听,则开启深度监视(deep: true):

watch(
user,
(newVal, oldVal) => {
console.log('user 内部发生变化')
},
{ deep: true }
)
user.value.age++ // ✅ 会触发
  • deep 监听时:

    • newVal === oldVal(同一个对象引用)
  • 适合监听嵌套属性变化;

  • 不要滥用,性能开销更大。

2.4.2.3. 只监听对象中的某个属性
Section titled “2.4.2.3. 只监听对象中的某个属性”

使用 getter 函数:

watch(
() => user.value.age,
(newAge, oldAge) => {
console.log('年龄变化:', oldAge, '', newAge)
}
)
  • 精准监听

  • 性能更好

  • 能正确拿到 oldValue / newValue

2.4.3. 监视reactive 定义对象类型数据

Section titled “2.4.3. 监视reactive 定义对象类型数据”

watch 监视 reactive 定义的对象时,默认进行深度监听,对象内部任意属性变化都会触发回调,但新旧值指向同一对象,通常更适合做“变化触发”而非“对比逻辑”。

import { reactive } from 'vue'
const user = reactive({
name: 'Tom',
age: 18
})

此时:

  • user 本身就是一个响应式对象

  • 没有 .value

  • 对象内部所有属性 默认都是深度响应式

import { watch } from 'vue'
watch(user, (newVal, oldVal) => {
console.log('user 发生变化')
})

如果只关心某一对象属性的变化,不要直接 watch 整个对象

watch(
() => user.age,
(newAge, oldAge) => {
console.log('年龄变化:', oldAge, '', newAge)
}
)

在 Vue 3 中,当需要同时监听多个数据源时,可以把它们放进一个数组中交给 watch。这是官方支持、也是最常用的多数据监听方式。

基本语法:

watch(
[source1, source2, source3],
(newValues, oldValues) => {
// ...
}
)
  • 数组中的每一项,都是一个合法的 watch 数据源

  • 任意一项变化,都会触发回调

const count = ref(0)
const user = reactive({ age: 18 })
watch(
[count, () => user.age],
([newCount, newAge]) => {
console.log(newCount, newAge)
}
)

立即执行一次回调,并自动追踪回调中用到的所有响应式数据;当这些数据中任意一个发生变化时,回调会再次执行。

watchEffect 会在创建时立即执行一次,并在执行过程中自动收集所使用的响应式数据作为依赖,当这些依赖中任意一个发生变化时,会重新执行回调函数,无需显式指定监听源。

import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => {
console.log('count:', count.value)
})
  • 第一次:立即执行

  • 之后:count.value 改变 → 自动重新执行

对比点watchwatchEffect
是否需要明确监听源✅ 是❌ 否
是否立即执行❌ 默认否✅ 默认是
是否能拿到 oldValue✅ 能❌ 不能
依赖收集方式手动指定自动收集
适合场景精准监听快速副作用

2.5.2. 依赖是如何“自动收集”的?

Section titled “2.5.2. 依赖是如何“自动收集”的?”
watchEffect(() => {
console.log(user.name)
console.log(count.value)
})

Vue 会自动追踪:

  • user.name

  • count.value

只要其中任意一个变化,整个 watchEffect 就会重新执行。

watchEffect 支持清理逻辑,常用于:

  • 取消请求

  • 清除定时器

  • 移除事件监听

watchEffect((onCleanup) => {
const timer = setInterval(() => {
console.log(count.value)
}, 1000)
onCleanup(() => {
clearInterval(timer)
})
})

每次重新执行前,都会先执行上一次的清理函数。

watchEffect(() => {
// 默认 flush: 'pre'
}, { flush: 'post' })
  • pre(默认):组件更新前

  • post:组件更新后(适合操作 DOM)

  • sync:同步执行(慎用)

ref可以作用在普通标签上(普通dom)也可以作用在组件标签上(实例对象)

在 Vue 中,ref 是一个特殊的 attribute,用于在模板中“注册”一个引用,使得在 JavaScript 逻辑中可以直接获取对应的 DOM 元素子组件实例

它与响应式系统中的 ref() 函数同名但用途不同:

  • 模板中的 ref:用于标记元素/组件;
  • 逻辑中的 ref():用于创建响应式变量。

<script setup> 为例:

<template>
<!-- 普通 DOM 元素 -->
<div ref="myDiv">Hello</div>
<!-- 子组件 -->
<ChildComponent ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
// 声明 ref 变量,名称需与模板中的 ref 属性一致
const myDiv = ref(null)
const childRef = ref(null)
onMounted(() => {
// 访问 DOM 元素
console.log(myDiv.value) // <div>Hello</div>
// 访问子组件实例(需子组件使用 defineExpose 暴露内容)
console.log(childRef.value)
})
</script>
  1. 作用在普通标签上 → 获取 DOM 元素
<input ref="inputEl" />

inputEl.value 是一个原生 HTMLInputElement 对象。

  1. 作用在组件标签上 → 获取组件实例
<MyButton ref="btn" />

btn.valueMyButton 组件的实例对象(但仅限于该组件通过 defineExpose 暴露了内容)。

  1. ref 的值在 onMounted 之后才可用

因为模板渲染发生在挂载阶段,所以必须在 onMounted 或之后才能访问 ref.value,否则为 null

在 Vue 3 的 <script setup> 语法中,defineProps 是一个编译时宏(macro),用于声明组件接收的 props。它不需要显式导入(可以省略),并且支持以下三种主要用法:

  1. 仅接收 prop 名称(无类型检查)
  2. 接收 + TypeScript 类型约束(推荐)
  3. 接收 + 类型约束 + 默认值 + 可选/必填控制(最完整)

defineProps<script setup> 中用于声明组件 props 的唯一方式。它返回一个包含所有传入 props 的对象。

const props = defineProps(/* ... */)
const props = defineProps(['list'])

所有 props 都是 any 类型,没有类型提示,也没有运行时校验

import type { Persons } from '@/types'
const props = defineProps<{ list: Persons }>()
  • 使用 泛型语法 明确指定每个 prop 的类型。
  • 提供完整的 TypeScript 类型推导和 IDE 智能提示
  • 如果父组件传递了错误类型的 list,TS 编译会报错(开发阶段即可发现)。

Persons 应该是一个数组类型,例如:

types.ts
export interface Person {
id: string
name: string
age: number
}
export type Persons = Person[]

2.7.4. 接收 + 类型 + 默认值 + 可选性

Section titled “2.7.4. 接收 + 类型 + 默认值 + 可选性”

当 prop 是可选的(即可能未传入),或者你需要提供默认值时,必须使用 withDefaults

import { withDefaults, defineProps } from 'vue'
import type { Persons } from '@/types'
const props = withDefaults(
defineProps<{
list?: Persons // 注意:这里用 ? 表示可选
}>(),
{
list: () => [
{ id: 'asdasg01', name: '小猪佩奇', age: 18 }
]
}
)

解析:

  1. 为什么默认值要用函数?

    因为 list 是一个引用类型(数组/对象),如果直接写 list: [...],会导致所有组件实例共享同一个数组(类似 Vue 2 中 data 必须是函数的问题)。
    所以必须用 工厂函数 () => [...] 来确保每个实例拥有独立的默认值。

  2. 可选 and 必填

    list: Persons → 必填(父组件必须传)
    list?: Persons → 可选(可不传,此时依赖默认值)

  3. withDefaults 的作用

    它是 Vue 3.3+ 提供的宏,专门用于在 TypeScript 泛型 props 声明中设置默认值。它会自动推导出最终 props 的类型(包括可选属性被“填充”后的类型)。

Vue 2 的生命周期是指一个 Vue 组件从创建到销毁的整个过程,在这个过程中会触发一系列的钩子函数,让开发者可以在特定阶段执行自定义逻辑。

beforeCreate

  • 触发时机:实例初始化之后,数据观测(data observer)和事件配置之前;
  • 可访问内容:无法访问 data、computed、methods、watch 等;
  • 常用场景:很少使用,可以在这加载 loading 事件。
beforeCreate() {
console.log(this.message); // undefined
console.log(this.$el); // undefined
}

created

  • 触发时机:实例创建完成,数据观测、属性和方法的运算、watch/event 事件回调已配置;
  • 可访问内容:可以访问 data、computed、methods、watch,但 DOM 未生成,$el 不可用;
  • 常用场景:发起异步请求获取数据、初始化数据。
created() {
console.log(this.message); // 可以访问
console.log(this.$el); // undefined
// 适合发起 ajax 请求
this.fetchData();
}

beforeMount

  • 触发时机:挂载开始之前,相关的 render 函数首次被调用;
  • 可访问内容:虚拟 DOM 已创建,但真实 DOM 还未生成;
  • 常用场景:很少使用。
beforeMount() {
console.log(this.$el); // undefined 或旧的 DOM
}

mounted

  • 触发时机:实例被挂载后调用,el 被新创建的 vm.$el 替换;
  • 可访问内容:可以访问到真实 DOM 元素;
  • 常用场景:操作 DOM、初始化第三方库(如图表库、地图等)、开启定时器;
  • 注意:不保证所有子组件也都被挂载,如需等待整个视图渲染完毕,可在 mounted 内使用 vm.$nextTick
mounted() {
console.log(this.$el); // 真实 DOM 元素
// 操作 DOM
this.$refs.myDiv.style.color = 'red';
// 初始化图表
this.initChart();
}

beforeUpdate

  • 触发时机:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前;
  • 可访问内容:可以访问到更新前的 DOM 状态;
  • 常用场景:在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
beforeUpdate() {
console.log('DOM 即将更新');
console.log(this.$el.textContent); // 旧的内容
}

updated

  • 触发时机:数据更改导致虚拟 DOM 重新渲染和打补丁后调用;
  • 可访问内容:DOM 已完成更新;
  • 常用场景:执行依赖于 DOM 的操作,但要避免在此更改状态(会导致无限循环);
  • 注意:不保证所有子组件也都被重新渲染,可使用 vm.$nextTick
updated() {
console.log('DOM 已更新');
console.log(this.$el.textContent); // 新的内容
// 注意:避免在这里修改数据,可能导致死循环
}

beforeDestroy

  • 触发时机:实例销毁之前调用,实例仍然完全可用;
  • 可访问内容:所有数据、方法、DOM 等都可访问;
  • 常用场景:清理定时器、取消事件监听、取消订阅、清理第三方库实例。
beforeDestroy() {
// 清理定时器
clearInterval(this.timer);
// 移除事件监听
window.removeEventListener('resize', this.handleResize);
// 销毁第三方库实例
this.chart.destroy();
}

destroyed

  • 触发时机:实例销毁后调用;
  • 可访问内容:所有指令解绑、事件监听器移除、子实例销毁;
  • 常用场景:很少使用,基本清理工作在 beforeDestroy 完成。
destroyed() {
console.log('组件已完全销毁');
}
new Vue()
beforeCreate (无法访问 data)
created (可访问 data,无法访问 DOM)
beforeMount (即将挂载 DOM)
mounted (DOM 已挂载,可操作 DOM)
┌─→ beforeUpdate (数据变化,DOM 未更新)
│ ↓
└── updated (DOM 已更新)
beforeDestroy (即将销毁,可清理资源)
destroyed (已销毁)

常见使用场景总结:

  • created: 发起 API 请求、初始化非 DOM 相关数据;

  • mounted: 操作 DOM、初始化第三方插件、启动定时器;

  • beforeDestroy: 清理定时器、取消订阅、移除事件监听;

  • activated/deactivated: keep-alive 组件的数据刷新和资源管理。

Vue 3 提供了两种 API 风格来使用生命周期:Options API(与 Vue 2 兼容)和 Composition API(新增)。

Vue 3 的 Options API 保留了 Vue 2 的大部分生命周期钩子,主要变化是重命名了销毁相关的钩子。

beforeUnmount (原 beforeDestroy)

  • 触发时机:组件卸载之前,实例仍完全可用;
  • 可访问内容:所有数据、方法、DOM 都可访问;
  • 使用场景:清理定时器、取消订阅、移除事件监听。

unmounted (原 destroyed)

  • 触发时机:组件卸载完成,所有子组件也已卸载;
  • 可访问内容:组件实例的所有指令已解绑,事件监听器已移除;
  • 使用场景:最终清理工作。
beforeUnmount() {
clearInterval(this.timer);
window.removeEventListener('resize', this.handleResize);
}

Composition API 在 setup() 函数中使用生命周期钩子,通过导入对应的函数来使用。

setup()beforeCreatecreated 之间执行,因此不需要这两个钩子的等价函数。

Options APIComposition API
beforeCreate不需要(在 setup 中直接编写)
created不需要(在 setup 中直接编写)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
实例:
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
const count = ref(0)
// 相当于 beforeCreate 和 created
console.log('setup 执行,初始化数据')
onBeforeMount(() => {
console.log('组件即将挂载')
})
onMounted(() => {
console.log('组件已挂载,可以访问 DOM')
// 操作 DOM、初始化第三方库
})
onBeforeUpdate(() => {
console.log('组件即将更新')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件即将卸载')
// 清理工作:清除定时器、取消订阅等
})
onUnmounted(() => {
console.log('组件已卸载')
})
return { count }
}
}

Composition API 完整流程:

创建组件实例
setup() 执行
onBeforeMount 钩子
初始化渲染,创建 DOM
onMounted 钩子
┌─→ onBeforeUpdate 钩子
│ ↓
│ 重新渲染 DOM
│ ↓
└── onUpdated 钩子
onBeforeUnmount 钩子
卸载组件,移除 DOM
onUnmounted 钩子

自定义 Hooks(也称为组合式函数 Composables)是 Vue 3 Composition API 的核心特性之一,用于封装和复用有状态的逻辑。通常以 use 开头命名(约定俗成)。它可以包含:

  • 响应式状态(ref, reactive)
  • 计算属性(computed)
  • 生命周期钩子(onMounted, onUnmounted 等)
  • 侦听器(watch, watchEffect)
  • 方法函数
// Vue 2 使用 mixin 复用逻辑
export default {
mixins: [mouseMixin, timerMixin],
// 问题:
// 1. 命名冲突:不知道 data 来自哪个 mixin
// 2. 隐式依赖:mixin 之间可能相互依赖
// 3. 难以维护:逻辑分散,不清晰
}
// Vue 3 使用组合式函数
import { useMouse } from './composables/useMouse'
import { useTimer } from './composables/useTimer'
export default {
setup() {
const { x, y } = useMouse()
const { count } = useTimer()
// 优点:
// 1. 来源清晰
// 2. 没有命名冲突
// 3. 逻辑集中,易维护
return { x, y, count }
}
}

示例:

hooks/useCounter.ts
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
increment,
decrement,
reset
}
}
// 使用
import { useCounter } from './hooks/useCounter'
export default {
setup() {
const { count, increment, decrement, reset } = useCounter(10)
return {
count,
increment,
decrement,
reset
}
}
}