Vue3核心知识点
1. 创建Vue工程
Section titled “1. 创建Vue工程”vite 是新一代前端构建工具,官网地址:https://vitejs.cn,vite的优势如下:
- 轻量快速的热重载(
HMR),能实现极速的服务启动。 - 对
TypeScript、JSX、CSS等支持开箱即用。 - 真正的按需编译,不再等待整个应用编译完成。
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? No7. 是否添加端到端测试方案√ 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 元素上,#app是 id 选择器,表示挂载的位置。
2. Vue3核心语法
Section titled “2. Vue3核心语法”2.1 Options API 与 Composition API
Section titled “2.1 Options API 与 Composition API”Options类型的 API,数据、方法、计算属性等,是分散在:data、methods、computed中的,若想新增或者修改一个需求,就需要分别修改:data、methods、computed,不便于维护和复用。
而Composition API就是把相关逻辑写在一起,而不是按 API 类型拆分。
2.2. setup实现Composition API
Section titled “2.2. setup实现Composition API”setup是Vue3中一个新的配置项,值是一个函数,它是Composition API 表演的舞台,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup中。
特点如下:
-
setup函数返回的对象中的内容,可直接在模板中使用; -
setup中访问this是undefined,不可使用; -
setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。
2.2.1. setup 的基本语法
Section titled “2.2.1. setup 的基本语法”<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>2.2.2. setup与Options API的关系
Section titled “2.2.2. setup与Options API的关系”Vue2 风格的配置项(data、methods、computed 等),可以访问 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 中,访问不到 data、methods、computed 等 Options API 配置。
示例:
export default { data() { return { count: 10 } },
setup() { console.log(this.count) // ❌ undefined }}-
setup执行时:-
组件实例 还没有创建
-
this是undefined
-
-
所以:
-
访问不到
data -
访问不到
methods -
访问不到
computed -
访问不到
this
-
2.2.3. setup语法糖
Section titled “2.2.3. setup语法糖”<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>2.2.4. 组件命名
Section titled “2.2.4. 组件命名”在 Vue 3 中使用:
<script setup>- 无法直接声明组件
name
原始写法:
<script setup></script>
<script>export default { name: 'Person'}</script>Vite插件扩展:
- 安装插件
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. 响应式数据
Section titled “2.3. 响应式数据”2.3.1. ref创建基本类型的响应式数据
Section titled “2.3.1. ref创建基本类型的响应式数据”ref是Vue 3中用于创建响应式数据 的 API,用来让 基本类型数据(number / string / boolean 等)也能拥有 响应式能力。
2.3.1.1. 基本用法
Section titled “2.3.1.1. 基本用法”- 创建响应式数据
import { ref } from 'vue'const count = ref(0)- 读取和修改值
count.value // 读取count.value++ // 修改
.value是必须的;
- 模板中使用(自动解包)
<p>{{ count }}</p><button @click="count++">+</button>模板中 不需要
.value。
2.3.2. reactive创建对象类型的响应式数据
Section titled “2.3.2. reactive创建对象类型的响应式数据”reactive 用于 创建响应式对象,通过 Proxy 对对象进行代理,实现数据变化自动更新视图。
专门用于对象 / 数组 / Map / Set 等引用类型
2.3.2.1. 基本用法
Section titled “2.3.2.1. 基本用法”- 创建响应式对象
import { reactive } from 'vue'
const state = reactive({ count: 0, user: { name: 'Alice' }})- 读取与修改
state.count++state.user.name = 'Bob'不需要
.value,直接操作属性。
2.3.2.2. 核心特点与局限
Section titled “2.3.2.2. 核心特点与局限”reactive 具有深度响应式的特点,当对象中存在嵌套结构时,即使修改的是深层属性(如 state.user.name = 'Tom'),依然可以触发视图更新。需要注意的是,reactive 只能接收对象类型的数据,不能用于基本类型(如 reactive(1)、reactive('hello') 均无效),其适用范围包括 Object、Array、Map 和 Set 等引用类型。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. ref与reactive对象整体替换与响应式
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 }})-
不改变对象引用
-
只修改对象内部属性
-
响应式能力不丢失
2.3.4.2. ref的正确整体替换方式
Section titled “2.3.4.2. ref的正确整体替换方式”state.value = { user: { name: 'Jerry', age: 20 }}通过
.value重新赋值整个对象
-
ref的响应式绑定在.value上 -
给
.value重新赋一个 新对象-
不会丢失响应式
-
Vue 会对新对象重新做响应式处理
-
-
与
reactive不同,ref天然支持对象整体替换
2.3.5. toRef与toRefs
Section titled “2.3.5. toRef与toRefs”toRef 和 toRefs 是 Vue 3 Composition API 中用于处理响应式对象属性的两个工具函数,主要用于在保持响应性的同时,将响应式对象的属性“解构”出来。
2.3.5.1. toRef
Section titled “2.3.5.1. toRef”作用:为响应式对象的某个属性创建一个 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.countcountRef.value++console.log(state.count) // 1
// 修改 state.count 也会影响 countRefstate.count++console.log(countRef.value) // 22.3.5.2. toRefs
Section titled “2.3.5.2. toRefs”作用:将一个响应式对象的所有属性转换为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 都是 refname.value = 'Bob'console.log(state.name) // 'Bob'典型用途:在组合函数中返回多个响应式状态,并允许调用者通过解构使用:
function useUser() { const user = reactive({ name: '', email: '' }) return toRefs(user)}
// 使用const { name, email } = useUser()2.3.6. computed计算属性
Section titled “2.3.6. computed计算属性”computed 是 Vue 3 Composition API 中用于创建计算属性(computed properties)的核心函数。它基于响应式依赖进行缓存的、自动更新的派生状态,非常适合用于从现有响应式数据中派生出新值。
computed 内部会自动追踪其用到的响应式数据(如 ref、reactive 对象的属性),只要依赖没有变化,多次访问 computed 的值不会重复执行计算函数,而是直接返回缓存结果。当依赖发生变化时,下一次访问 computed 值会重新计算。
2.3.6.1. 只读计算属性
Section titled “2.3.6.1. 只读计算属性”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访问其值。
2.3.6.2. 可写计算属性
Section titled “2.3.6.2. 可写计算属性”有时希望反向设置计算属性,这时可以提供一个 getter 和 setter:
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中通常需要将新值“拆解”回原始响应式数据。
2.3.6.3. 与 methods 的区别
Section titled “2.3.6.3. 与 methods 的区别”| 特性 | computed | method(函数) |
|---|---|---|
| 缓存 | ✅ 有缓存,依赖不变则不重算 | ❌ 每次调用都执行 |
| 响应式 | ✅ 自动追踪依赖并更新 | ❌ 需手动调用 |
| 模板中使用 | 无需加 () | 需要加 () |
2.4. watch函数
Section titled “2.4. watch函数”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:旧值
-
2.4.1.1. 触发时机
Section titled “2.4.1.1. 触发时机”默认行为:
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)})2.4.2.2. 深度监视
Section titled “2.4.2.2. 深度监视”如果希望对象内部属性变化也能被监听,则开启深度监视(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) })2.4.4. 监视数组
Section titled “2.4.4. 监视数组”在 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) })2.5. watchEffect 函数
Section titled “2.5. watchEffect 函数”立即执行一次回调,并自动追踪回调中用到的所有响应式数据;当这些数据中任意一个发生变化时,回调会再次执行。
watchEffect会在创建时立即执行一次,并在执行过程中自动收集所使用的响应式数据作为依赖,当这些依赖中任意一个发生变化时,会重新执行回调函数,无需显式指定监听源。
import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => { console.log('count:', count.value)})-
第一次:立即执行
-
之后:
count.value改变 → 自动重新执行
2.5.1. 和 watch 的核心区别
Section titled “2.5.1. 和 watch 的核心区别”| 对比点 | watch | watchEffect |
|---|---|---|
| 是否需要明确监听源 | ✅ 是 | ❌ 否 |
| 是否立即执行 | ❌ 默认否 | ✅ 默认是 |
| 是否能拿到 oldValue | ✅ 能 | ❌ 不能 |
| 依赖收集方式 | 手动指定 | 自动收集 |
| 适合场景 | 精准监听 | 快速副作用 |
2.5.2. 依赖是如何“自动收集”的?
Section titled “2.5.2. 依赖是如何“自动收集”的?”watchEffect(() => { console.log(user.name) console.log(count.value)})Vue 会自动追踪:
-
user.name -
count.value
只要其中任意一个变化,整个 watchEffect 就会重新执行。
2.5.3. 清理副作用(onCleanup)
Section titled “2.5.3. 清理副作用(onCleanup)”watchEffect 支持清理逻辑,常用于:
-
取消请求
-
清除定时器
-
移除事件监听
watchEffect((onCleanup) => { const timer = setInterval(() => { console.log(count.value) }, 1000)
onCleanup(() => { clearInterval(timer) })})每次重新执行前,都会先执行上一次的清理函数。
2.5.4. 执行时机(flush)
Section titled “2.5.4. 执行时机(flush)”watchEffect(() => { // 默认 flush: 'pre'}, { flush: 'post' })-
pre(默认):组件更新前 -
post:组件更新后(适合操作 DOM) -
sync:同步执行(慎用)
ref可以作用在普通标签上(普通dom)也可以作用在组件标签上(实例对象)
2.6. 模板 ref(Template Ref)
Section titled “2.6. 模板 ref(Template Ref)”在 Vue 中,ref 是一个特殊的 attribute,用于在模板中“注册”一个引用,使得在 JavaScript 逻辑中可以直接获取对应的 DOM 元素 或 子组件实例。
它与响应式系统中的 ref() 函数同名但用途不同:
- 模板中的
ref:用于标记元素/组件; - 逻辑中的
ref():用于创建响应式变量。
2.6.1. 基本用法
Section titled “2.6.1. 基本用法”以 <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>2.6.2. 关键特性
Section titled “2.6.2. 关键特性”- 作用在普通标签上 → 获取 DOM 元素
<input ref="inputEl" />
inputEl.value是一个原生HTMLInputElement对象。
- 作用在组件标签上 → 获取组件实例
<MyButton ref="btn" />
btn.value是MyButton组件的实例对象(但仅限于该组件通过defineExpose暴露了内容)。
- ref 的值在 onMounted 之后才可用
因为模板渲染发生在挂载阶段,所以必须在 onMounted 或之后才能访问 ref.value,否则为 null。
2.7. props
Section titled “2.7. props”在 Vue 3 的 <script setup> 语法中,defineProps 是一个编译时宏(macro),用于声明组件接收的 props。它不需要显式导入(可以省略),并且支持以下三种主要用法:
- 仅接收 prop 名称(无类型检查)
- 接收 + TypeScript 类型约束(推荐)
- 接收 + 类型约束 + 默认值 + 可选/必填控制(最完整)
2.7.1. defineProps 基础介绍与使用
Section titled “2.7.1. defineProps 基础介绍与使用”defineProps 是 <script setup> 中用于声明组件 props 的唯一方式。它返回一个包含所有传入 props 的对象。
const props = defineProps(/* ... */)2.7.2. 仅接收(无类型限制)
Section titled “2.7.2. 仅接收(无类型限制)”const props = defineProps(['list'])所有 props 都是
any类型,没有类型提示,也没有运行时校验。
2.7.3. 接收 + 限定类型
Section titled “2.7.3. 接收 + 限定类型”import type { Persons } from '@/types'
const props = defineProps<{ list: Persons }>()- 使用 泛型语法 明确指定每个 prop 的类型。
- 提供完整的 TypeScript 类型推导和 IDE 智能提示。
- 如果父组件传递了错误类型的
list,TS 编译会报错(开发阶段即可发现)。
Persons 应该是一个数组类型,例如:
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 } ] })解析:
-
为什么默认值要用函数?
因为
list是一个引用类型(数组/对象),如果直接写list: [...],会导致所有组件实例共享同一个数组(类似 Vue 2 中 data 必须是函数的问题)。
所以必须用 工厂函数() => [...]来确保每个实例拥有独立的默认值。 -
可选 and 必填
list: Persons→ 必填(父组件必须传)
list?: Persons→ 可选(可不传,此时依赖默认值) -
withDefaults的作用它是 Vue 3.3+ 提供的宏,专门用于在 TypeScript 泛型 props 声明中设置默认值。它会自动推导出最终
props的类型(包括可选属性被“填充”后的类型)。
2.8. 生命周期
Section titled “2.8. 生命周期”2.8.1. Vue 2 生命周期
Section titled “2.8.1. Vue 2 生命周期”Vue 2 的生命周期是指一个 Vue 组件从创建到销毁的整个过程,在这个过程中会触发一系列的钩子函数,让开发者可以在特定阶段执行自定义逻辑。
2.8.1.1. 创建阶段 (Initialization)
Section titled “2.8.1.1. 创建阶段 (Initialization)”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();}2.8.1.2. 挂载阶段 (Mounting)
Section titled “2.8.1.2. 挂载阶段 (Mounting)”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();}2.8.1.3. 更新阶段 (Updating)
Section titled “2.8.1.3. 更新阶段 (Updating)”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); // 新的内容 // 注意:避免在这里修改数据,可能导致死循环}2.8.1.4. 销毁阶段 (Destruction)
Section titled “2.8.1.4. 销毁阶段 (Destruction)”beforeDestroy
- 触发时机:实例销毁之前调用,实例仍然完全可用;
- 可访问内容:所有数据、方法、DOM 等都可访问;
- 常用场景:清理定时器、取消事件监听、取消订阅、清理第三方库实例。
beforeDestroy() { // 清理定时器 clearInterval(this.timer); // 移除事件监听 window.removeEventListener('resize', this.handleResize); // 销毁第三方库实例 this.chart.destroy();}destroyed
- 触发时机:实例销毁后调用;
- 可访问内容:所有指令解绑、事件监听器移除、子实例销毁;
- 常用场景:很少使用,基本清理工作在 beforeDestroy 完成。
destroyed() { console.log('组件已完全销毁');}2.8.1.5. 生命周期执行顺序图
Section titled “2.8.1.5. 生命周期执行顺序图”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 组件的数据刷新和资源管理。
2.8.2. Vue 3 生命周期
Section titled “2.8.2. Vue 3 生命周期”Vue 3 提供了两种 API 风格来使用生命周期:Options API(与 Vue 2 兼容)和 Composition API(新增)。
2.8.2.1. Options API 生命周期
Section titled “2.8.2.1. Options API 生命周期”Vue 3 的 Options API 保留了 Vue 2 的大部分生命周期钩子,主要变化是重命名了销毁相关的钩子。
beforeUnmount (原 beforeDestroy)
- 触发时机:组件卸载之前,实例仍完全可用;
- 可访问内容:所有数据、方法、DOM 都可访问;
- 使用场景:清理定时器、取消订阅、移除事件监听。
unmounted (原 destroyed)
- 触发时机:组件卸载完成,所有子组件也已卸载;
- 可访问内容:组件实例的所有指令已解绑,事件监听器已移除;
- 使用场景:最终清理工作。
beforeUnmount() { clearInterval(this.timer); window.removeEventListener('resize', this.handleResize);}2.8.2.2. Composition API 生命周期
Section titled “2.8.2.2. Composition API 生命周期”Composition API 在 setup() 函数中使用生命周期钩子,通过导入对应的函数来使用。
setup() 在 beforeCreate 和 created 之间执行,因此不需要这两个钩子的等价函数。
| Options API | Composition API |
|---|---|
beforeCreate | 不需要(在 setup 中直接编写) |
created | 不需要(在 setup 中直接编写) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
| 实例: |
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 钩子2.9. 自定义 Hooks
Section titled “2.9. 自定义 Hooks”自定义 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 } }}示例:
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 } }}