Vue 基础
四个阶段:创建、挂载、更新、卸载
Section titled “四个阶段:创建、挂载、更新、卸载”创建阶段:setup(组合式)、beforeCreate -> created(选项式)
挂载阶段:beforeMount -> mounted
更新阶段:beforeUpdate -> updated
卸载阶段:beforeUnmount -> unmounted
父子组件生命周期
Section titled “父子组件生命周期”如下图:

有如下结论:
-
创建/挂载阶段:父组件挂载开始时才会对子组件进行创建以及挂载,父组件下所有子组件挂载完成后,才会出发父组件的
mounted钩子 -
更新/卸载阶段:父组件执行更新/卸载 -> 子组件执行更新/卸载 -> 子组件更新/卸载完成 -> 父组件更新/卸载完成
ref 和 reactive
Section titled “ref 和 reactive”组合式API中对响应式变量进行定义有两个API: ref 和 reactive。
两者用法有如下差异:
- 使用
ref声明的变量内部数据在setup函数中需要通过.value进行访问,但在组件template中使用则不需要;调用reactive返回一个Proxy类型的对象,可以直接访问属性值。 reactive只能用于对象类型, 不能用于基本数据类型
计算属性对象是一种特殊的响应式对象,在组合式API中,传入computed的是一个函数,函数的返回值是一个根据其他响应式变量(定义的ref/reactive变量/组件外部传入的props等)计算的结果。调用computed返回的结果是一个只读的Ref对象,在setup函数中同样需要通过.value属性访问内部值。
数据绑定方式
Section titled “数据绑定方式”单向绑定:v-bind(父组件传入子组件)
双向绑定:v-model(语法糖,等价于v-bind + 自定义事件)
组合式API相关能力:defineModel
优化API:shallowRef、shallowReactive
Section titled “优化API:shallowRef、shallowReactive”vue3的响应式系统默认是深度的,对于层级较深的复杂对象而言,可能会造成一部分性能负担。
有的场景下我们不需要对复杂对象进行深层次监听,为此vue3提供了shallowRef、shallowReactive这两个API。
-
shallowRef-
场景:大型对象/数组的整体替换、第三方库实例(如
DOM元素)管理、与渲染无关的临时状态。 -
收益:跳过深层响应式转换,仅响应
.value引用变化,提升初始化性能,减少内存开销。
-
-
shallowReactive-
场景:仅需响应顶层字段变化(如高频更新的坐标数据)、配合不可变数据结构、表单配置对象。
-
收益:只追踪第一层属性,避免嵌套属性的冗余依赖收集,优化高频更新性能。
-
通过这两个API能精准控制响应层级,避免深度递归的性能损耗,适用于数据量大而复杂、更新频繁或非深度响应场景。
-
为什么
data属性是一个函数而不是一个对象?在
vue2中,data属性定义为函数,vue会把函数调用的结果作为组件实例对象的数据,保证同一组件的不同实例对象数据不互相干扰。另外
vue3目前已经不再支持data属性直接定义一个对象,会有警告:The data option must be a function. Plain object usage is no longer supported.。源码见:core/packages/runtime-core/src /componentOptions.ts
场景:多组件共享数据、某个组件数据更新后其他组件需要进行感知
分类:
-
父子组件通信:
-
父 -> 子:
props、provide/inject -
子 -> 父: 自定义事件(
emit/on)
-
-
跨级组件通信:
-
根组件 -> 子组件:
provide/inject、attrs -
其他情形:状态管理工具(
Vuex、Pinia)
-
条件渲染 v-if
Section titled “条件渲染 v-if”v-if用于根据表达式的真假值来有条件地渲染一块内容。这块内容只会在指令的表达式返回真值时被渲染。
v-if是“真实的”条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。它也是惰性的:如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。
因为v-if是一个指令,它必须附着在一个元素上。如果想切换多个元素,可以用一个<template>元素作为不可见的包装器,并在其上使用v-if。最终的渲染结果将不包含<template>元素。如:
<template v-if="isVisible"> <h1>标题</h1> <p>段落一</p> <p>段落二</p></template>列表渲染 v-for
Section titled “列表渲染 v-for”v-for指令用于基于一个数组或对象来循环渲染一个列表。
在v-for循环中,Vue强烈建议为每个迭代的元素提供一个唯一的key。这个key是Vue用来识别节点、跟踪每个节点身份、重用和重新排序现有元素的重要机制。它对于列表的性能优化和状态维护(如表单输入值)至关重要。
为了优化大型列表渲染的场景,Vue还提供了v-memo指令。使用例子如下:
<template> <div> <div v-for="user in users" :key="user.id" v-memo="[user.id, user.name, user.avatarUrl, user.email, user.department]" class="user-item" > <Avatar :src="user.avatarUrl" /> <span>{{ user.name }}</span> <span>{{ user.email }}</span> <span>{{ user.department }}</span> <!-- ... 其他复杂内容 ... --> </div> </div></template>对于每一个列表项,Vue会缓存它最后一次渲染的结果。在下次更新时,它会对比v-memo依赖数组中所有的值(user.id, user.name 等):
-
如果所有值都完全一样:Vue会完全跳过这个
<div>及其所有子组件(如<Avatar>)的虚拟DOM生成和diff过程,直接复用之前的DOM。 -
如果其中有任何一个值发生了变化:
Vue会像正常一样重新渲染该列表项。
元素显示/隐藏 v-show
Section titled “元素显示/隐藏 v-show”v-show用于控制元素的显示/隐藏。
当v-show传入值为true,对应元素样式会变为display: none。由于只是样式的切换,所以开销很低。但正是因为v-show只是调整元素的样式,所以不能使用于<template>标签。
适用于元素显示/隐藏切换频繁的场景。
插槽 Slot
Section titled “插槽 Slot”插槽是一种让父组件向子组件传递内容的机制,常用的有四种:
| 插槽类型 | 描述 | 语法示例 |
|---|---|---|
| 默认插槽 | 未命名的插槽,用于接收未指定插槽名称的内容。 | 子组件:<slot>默认内容</slot>父组件: <ChildComp>内容</ChildComp> 或 <template #default>内容</template> |
| 具名插槽 | 具有名称的插槽,允许将内容定向到组件中的特定位置。 | 子组件:<slot name="header"></slot>父组件: <template #header>内容</template> |
| 作用域插槽 | 允许子组件向插槽传递数据,父组件可以使用这些数据来定制插槽内容的渲染。 | 子组件:<slot :data="item"></slot>父组件: <template #default="slotProps">{{ slotProps.data }}</template> |
| 动态插槽 | 插槽的名称可以由变量动态决定,提供更大的灵活性。 | 父组件:<template #[dynamicSlotName]>内容</template> |
nextTick 方法
Section titled “nextTick 方法”-
获取更新后的
DOM状态当响应式数据变化后,
Vue会异步更新DOM。nextTick允许你在DOM更新完成后 执行回调函数,从而安全地访问最新的DOM元素或组件状态。 -
解决异步更新导致的逻辑依赖问题
确保代码在
Vue完成一轮数据变更到DOM渲染的流程后执行,避免因DOM未更新而引发的逻辑错误(如读取旧DOM尺寸/位置)。
-
在响应式数据变化后
当修改了组件的
ref、reactive等响应式数据时,Vue会启动一个异步更新队列。nextTick回调会在 该队列中的DOM更新任务全部完成后 触发。 -
在生命周期钩子中
例如
updated()钩子中修改数据后,若需立即操作更新后的DOM,应使用nextTick。 -
在事件处理函数中
若事件处理函数内修改了数据并需要立即操作新
DOM(如聚焦输入框),需在nextTick中执行。
-
Vue收集同一事件循环内的所有数据变更,合并为一次异步更新(微任务)。 -
nextTick将回调函数推入微任务队列(优先使用Promise.then,降级方案为setTimeout)。 -
当前同步代码执行完毕 → 微任务队列执行(
DOM更新) →nextTick回调执行。
组合式 API
Section titled “组合式 API”组合式API是一套基于函数的API,它允许我们通过导入函数和使用它们的方式来组织组件的逻辑,而不是通过强制性地将代码分布到不同的选项(如data, methods, mounted)中来组织。
相较于选项式API,有如下优点:
-
更好的逻辑组织与代码可读性:组合式
API让我们可以将同一个逻辑关注点的所有代码集中在一起。你可以将属于某个功能的所有响应式数据、计算属性、方法和生命周期钩子都写在一块。这样,要理解一个功能,你只需要看那一个代码块即可,无需上下翻找。这使得代码更易于阅读和维护。如:
<script setup>import { ref, onMounted } from 'vue';// 功能A的逻辑(可以甚至提取到一个独立的函数中)function useFeatureA() {const dataA = ref(null);function methodA() { /* ... */ };onMounted(() => { /* 初始化A */ });return { dataA, methodA };}// 功能B的逻辑function useFeatureB() {const dataB = ref(null);function methodB() { /* ... */ };onMounted(() => { /* 初始化B */ });return { dataB, methodB };}const { dataA, methodA } = useFeatureA();const { dataB, methodB } = useFeatureB();return {dataA, methodA,dataB, methodB};</script> -
更强的逻辑复用能力:相较于选项式
API的Mixin,组合式函数进行代码复用更为灵活清晰。组合式函数有如下几个特点:-
命名可定制: 在解构时可以重命名,避免冲突。
-
数据来源清晰: 从哪个组合式函数返回了什么值,一目了然。
-
可传参、可交互: 组合式函数可以接受参数,不同的组合式函数可以相互嵌套使用,灵活性极高。
-
-
更小的生产包体积:组合式函数下,生命周期/响应式变量定义等都是通过组合式函数实现,按需引入即可,对
Tree-shaking更为友好。
其他常用 API
Section titled “其他常用 API”watch 和 watchEffect
Section titled “watch 和 watchEffect”当我们需要在状态改变时去做一些操作的时候,就需要使用侦听器。Vue提供了watch和watchEffect这两个API来进行侦听器注册。
watch的功能是侦听一个或多个特定的响应式数据源,并在这些数据源发生变化时执行一个回调函数。
如:
import { ref, watch } from 'vue'
const count = ref(0)const state = reactive({ name: 'Vue', version: 3 })
// 1. 侦听一个 refwatch(count, (newValue, oldValue) => { console.log(`count 从 ${oldValue} 变成了 ${newValue}`)})
// 2. 侦听一个 getter 函数(用于监听 reactive 对象的某个属性)watch( () => state.name, (newName, oldName) => { console.log(`名字从 ${oldName} 变成了 ${newName}`) })
// 3. 侦听多个源(数组形式)watch([count, () => state.name], ([newCount, newName], [oldCount, oldName]) => { console.log(`多个值变了: count=${newCount}, name=${newName}`)})
// 4. 配置选项watch(count, (newValue, oldValue) => { // 回调函数}, { immediate: true, // 立即执行一次 deep: true, // 深度遍历,用于监听对象或数组内部的嵌套变化 flush: 'post' // 控制回调的触发时机,例如在 DOM 更新后触发})组件初始化时默认不会执行侦听器,可以通过watch第三个参数指定immediate: true。
watchEffect会立即执行传入的函数,同时在这个过程中自动追踪函数内部所依赖的所有响应式属性(类似计算属性computed)。并在这些依赖发生任何变化时,重新运行该函数。
watchEffect有如下几个特点:
-
自动依赖收集:你不需要手动声明依赖,
Vue会自动分析回调函数体并收集所有被使用的响应式属性。 -
立即执行:在组件初始化时或
watchEffect被创建时,会立即运行一次,用于收集依赖。 -
无法获取变化前的值:回调函数只会在依赖变化后运行,你只能拿到当前的最新值。
使用示例如下:
import { ref, watchEffect } from 'vue'
const count = ref(0)const name = ref('Alice')
// 立即执行,并自动追踪 count.value 和 name.valuewatchEffect(() => { // 这个函数体用到了哪些响应式数据,Vue 就会自动把它们作为依赖 console.log(`效果触发了!Count: ${count.value}, Name: ${name.value}`) // 常见的用例:发送 API 请求 // fetch(`/api/user/${name.value}?count=${count.value}`)})
// 当 count 或 name 改变时,上面的 effect 会再次运行。useTemplateRef
Section titled “useTemplateRef”在Vue 3.5以前的版本,我们如果想要获取模板内元素/组件的引用,需要在setup定义一个Ref对象变量,且变量名需要和组件/元素的ref属性传入的字符串对齐。
Vue 3.5以后,可以使用useTemplateRef获取元素/组件的引用,如:
<script setup>import { useTemplateRef, onMounted } from 'vue'
// 第一个参数必须与模板中的 ref 值匹配const input = useTemplateRef('my-input')
onMounted(() => { input.value.focus()})</script>
<template> <input ref="my-input" /></template>使用该API的好处有如下几个:
-
- 增加代码可读性:
Ref对象一般用于定义响应式变量,但我们在获取组件/元素引用时也是用Ref定义。相较于这种方式,使用useTemplateRef定义在语义上更为清晰。
- 增加代码可读性:
-
TypeScript自动推导类型:使用useTemplateRef创建的ref类型可以基于匹配的ref attribute所在的元素自动推断为静态类型,无需手动标注类型。(参考:TypeScript 与组合式 API | Vue.js)
provide/inject
Section titled “provide/inject”provide和inject是一对用于实现 “依赖注入” 的 API。它们允许一个祖先组件向其所有子孙组件(无论层级有多深)提供一个依赖(数据、方法、甚至整个实例),而不需要通过props逐层传递。
一般用于祖先组件给子孙组件提供共享数据的场景。如:
<!-- 祖先组件 (Provider) --><script setup>import { provide, ref, reactive } from 'vue'
// 提供静态值provide('siteName', 'My Awesome Site')
// 提供响应式数据const user = reactive({ name: 'Alice', id: 123 })provide('user', user)
// 提供方法function logout() { // ... 退出登录逻辑}provide('logout', logout)
// 提供 refconst count = ref(0)provide('count', count)</script>
<!-- 子孙组件 (Consumer) --><script setup>import { inject } from 'vue'
// 注入默认值,避免未提供时值为 undefinedconst siteName = inject('siteName', 'Default Site Name')const user = inject('user')const logout = inject('logout')const count = inject('count')
// 使用注入的值console.log(siteName.value)user.name = 'Bob' // 修改会影响所有注入此数据的组件</script>Teleport
Section titled “Teleport”Teleport是Vue3内置的一个组件,它可以将组件的内容”传送”到DOM中的其他位置,而不受父组件DOM结构的限制。
如:
<template> <div> <!-- 其他内容 --> <Teleport to="body"> <Modal v-if="showModal"> 这是一个模态框 </Modal> </Teleport> </div></template>通过to属性可以指定组件渲染的真实DOM挂载到的目标容器。
一般如下场景会使用Teleport:
-
模态框/对话框
-
通知/提示消息
-
加载遮罩
-
工具提示
-
下拉菜单(在某些布局中)
KeepAlive
Section titled “KeepAlive”KeepAlive是Vue3内置的一个组件,它可以缓存包裹在其中的动态组件实例,避免组件在切换时被重复创建和销毁。
当组件被KeepAlive包裹时:
- 组件实例会被缓存而不是销毁
- 再次显示时会复用缓存的实例,而不是重新创建
- 组件的状态(如表单输入、滚动位置等)会被保留
<template> <KeepAlive> <component :is="currentComponent" /> </KeepAlive></template>KeepAlive支持以下属性来控制缓存行为:
- include - 指定哪些组件需要被缓存
<!-- 缓存名为 ComponentA 和 ComponentB 的组件 --><KeepAlive include="ComponentA,ComponentB"> <component :is="currentComponent" /></KeepAlive>
<!-- 使用数组形式 --><KeepAlive :include="['ComponentA', 'ComponentB']"> <component :is="currentComponent" /></KeepAlive>
<!-- 使用正则表达式 --><KeepAlive :include="/Component[AB]/"> <component :is="currentComponent" /></KeepAlive>- exclude - 指定哪些组件不需要被缓存
<!-- 除了 ComponentC 之外的组件都会被缓存 --><KeepAlive exclude="ComponentC"> <component :is="currentComponent" /></KeepAlive>- max - 限制最多缓存多少个组件实例
<!-- 最多缓存 3 个组件实例 --><KeepAlive :max="3"> <component :is="currentComponent" /></KeepAlive>生命周期钩子
Section titled “生命周期钩子”被KeepAlive缓存的组件会有两个特殊的生命周期钩子:
onActivated- 组件被激活时调用(从缓存中恢复)onDeactivated- 组件被停用时调用(进入缓存状态)
<script setup>import { onActivated, onDeactivated } from 'vue'
onActivated(() => { console.log('组件被激活') // 可以在这里刷新数据、重新订阅事件等})
onDeactivated(() => { console.log('组件被停用') // 可以在这里清理定时器、取消订阅等})</script>常见使用场景
Section titled “常见使用场景”-
标签页切换 - 保持各个标签页的状态和数据
-
表单填写 - 避免用户在页面切换时丢失已填写的表单数据
-
列表页面 - 保持滚动位置、搜索条件、分页状态等
-
复杂组件 - 避免重复渲染耗时的组件(如图表、富文本编辑器等)
-
路由缓存 - 结合
vue-router缓存整个页面组件