📖 目录
- [一、Vue3 简介](#一、Vue3 简介)
- [二、创建 Vue3 工程](#二、创建 Vue3 工程)
- [三、Vue3 核心语法](#三、Vue3 核心语法)
- [四、Vue Router 路由](#四、Vue Router 路由)
- [五、Pinia 状态管理](#五、Pinia 状态管理)
- 六、组件通信
- [七、其他 API](#七、其他 API)
- [八、Vue3 新组件](#八、Vue3 新组件)
- [九、💡 思考与创意用法](#九、💡 思考与创意用法)
一、Vue3 简介
2020 年 9 月 18 日发布,代号 One Piece (海贼王)
经历:4800+ 次提交、40+ 个 RFC、600+ 次 PR、300+ 贡献者
1.1 性能提升
| 指标 | 提升幅度 |
|---|---|
| 打包体积 | 减少 41% |
| 初次渲染 | 快 55% |
| 更新渲染 | 快 133% |
| 内存占用 | 减少 54% |
1.2 源码升级
- 使用 Proxy 代替
defineProperty实现响应式 - 重写虚拟 DOM 的实现和 Tree-Shaking
1.3 拥抱 TypeScript
Vue3 对 TypeScript 的支持更加完善,类型推导更准确。
1.4 新特性一览
| 类别 | 内容 |
|---|---|
| Composition API | setup、ref、reactive、computed、watch |
| 新内置组件 | Fragment(多根节点)、Teleport、Suspense |
| 其他改变 | 新生命周期钩子、data 必须为函数、移除 keyCode 修饰符 |
💡 思考 :Vue3 的核心设计哲学是 "更好的逻辑复用" 和 "更细粒度的响应式"。Composition API 让相关逻辑聚合在一起,而非像 Options API 那样分散在 data/methods/computed 中。
二、创建 Vue3 工程
2.1 基于 vue-cli 创建(已进入维护模式)
bash
vue --version # 确保 @vue/cli >= 4.5.0
npm install -g @vue/cli
vue create vue_test # 选择 3.x
cd vue_test && npm run serve
2.2 基于 Vite 创建(⭐ 推荐)
Vite 优势:轻量快速热重载、开箱即用支持 TS/JSX/CSS、真正的按需编译
bash
npm create vue@latest
配置选项:
| 选项 | 说明 |
|---|---|
| Project name | 项目名称 |
| Add TypeScript | ✅ 推荐 |
| Add Vue Router | 按需 |
| Add Pinia | 按需 |
| Add Vitest | 按需 |
| Add ESLint | ✅ 推荐 |
2.3 关键变化
Vite 项目中 index.html 是入口文件(在项目最外层)
Vue3 通过 createApp 函数创建应用实例
Vue3 模板中可以没有根标签(Fragment)
💡 创意用法 :利用 Vite 的
npm create vue@latest快速搭建原型项目,配合--template参数可以自定义项目模板,团队协作时统一项目结构。
三、Vue3 核心语法
3.1 Options API vs Composition API
┌─────────────────────────────────────────────────┐
│ Options API (Vue2) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ data │ │ methods │ │computed │ ← 分散 │
│ └──────────┘ └──────────┘ └──────────┘ │
│ 同一功能的代码散落在不同配置项中 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Composition API (Vue3) │
│ ┌─────────────────────────────────┐ │
│ │ 功能A: data + methods + computed │ ← 聚合 │
│ ├─────────────────────────────────┤ │
│ │ 功能B: data + methods + computed │ ← 聚合 │
│ └─────────────────────────────────┘ │
│ 相关功能的代码组织在一起 │
└─────────────────────────────────────────────────┘
3.2 setup --- Composition API 的舞台
核心特点:
| 特性 | 说明 |
|---|---|
| 返回对象 | 属性/方法可在模板中直接使用 |
| this 访问 | undefined(Vue3 不再依赖 this) |
| 执行时机 | 在 beforeCreate 之前调用 |
| setup 语法糖 | <script setup lang="ts"> 简化写法 |
setup 语法糖 + 组件命名:
bash
npm i vite-plugin-vue-setup-extend -D
ts
// vite.config.ts
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [VueSetupExtend()]
})
vue
<script setup lang="ts" name="Person">
// 直接编写逻辑,无需 return
</script>
3.3 ref --- 创建基本类型响应式数据
ts
import { ref } from 'vue'
let name = ref('张三') // RefImpl 实例对象
let age = ref(18)
// JS 中操作需要 .value
name.value = '李四'
age.value += 1
// 模板中直接使用,无需 .value
// {{ name }}、{{ age }}
⚠️ 注意 :
name本身不是响应式的,name.value才是响应式的。
3.4 reactive --- 创建对象类型响应式数据
ts
import { reactive } from 'vue'
let car = reactive({ brand: '奔驰', price: 100 })
let games = reactive([
{ id: '01', name: '英雄联盟' },
{ id: '02', name: '王者荣耀' }
])
// 深层次响应式 ------ 嵌套属性也是响应式的
car.price += 10
games[0].name = '流星蝴蝶剑'
3.5 ref 也能处理对象类型
ts
let car = ref({ brand: '奔驰', price: 100 })
// 内部实际调用了 reactive
car.value.price += 10 // 注意需要 .value
3.6 🔑 ref vs reactive 对比
| 维度 | ref |
reactive |
|---|---|---|
| 适用类型 | 基本类型 + 对象类型 | 仅对象类型 |
| 操作方式 | 需要 .value |
直接访问 |
| 重新赋值 | ✅ 可以(xxx.value = newObj) |
❌ 失去响应式(需用 Object.assign) |
| 深层响应 | 对象类型自动深层 | 自动深层 |
使用原则:
📌 基本类型 → 必须用 ref
📌 对象类型、层级不深 → ref / reactive 均可
📌 对象类型、层级较深 → 推荐 reactive
3.7 toRefs 与 toRef
ts
import { reactive, toRefs, toRef } from 'vue'
let person = reactive({ name: '张三', age: 18, gender: '男' })
// 批量转换:保持响应式能力
let { name, gender } = toRefs(person)
// 单个转换
let age = toRef(person, 'age')
// 使用时仍需 .value
name.value += '~'
age.value += 1
💡 思考 :
toRefs在解构 reactive 对象时特别有用,避免解构后丢失响应式。这是 Vue3 中 "保持响应式引用链" 的关键手段。
3.8 computed --- 计算属性
ts
import { ref, computed } from 'vue'
let firstName = ref('zhang')
let lastName = ref('san')
// 只读计算属性
let fullName = computed(() => firstName.value + '-' + lastName.value)
// 可读可写计算属性
let fullName = computed({
get() { return firstName.value + '-' + lastName.value },
set(val) {
firstName.value = val.split('-')[0]
lastName.value = val.split('-')[1]
}
})
3.9 watch --- 侦听器(五种情况)
情况一:监视 ref 定义的基本类型
ts
let sum = ref(0)
watch(sum, (newVal, oldVal) => {
console.log('sum 变化了', newVal, oldVal)
if (newVal >= 10) stopWatch() // 停止监视
})
情况二:监视 ref 定义的对象类型
ts
let person = ref({ name: '张三', age: 18 })
// 默认监视地址值,需手动开启深度监视
watch(person, (newVal, oldVal) => {
console.log('person 变化了', newVal, oldVal)
}, { deep: true })
⚠️ 修改对象内部属性时,
newVal和oldVal是同一个对象。
情况三:监视 reactive 定义的对象类型
ts
let person = reactive({ name: '张三', age: 18 })
// 默认开启深度监视
watch(person, (newVal, oldVal) => {
console.log('person 变化了', newVal, oldVal)
})
情况四:监视对象中的某个属性
ts
let person = reactive({ name: '张三', age: 18, car: { c1: '奔驰', c2: '宝马' } })
// 基本类型属性 → 必须写成函数
watch(() => person.name, (newVal, oldVal) => { /* ... */ })
// 对象类型属性 → 推荐写成函数 + deep
watch(() => person.car, (newVal, oldVal) => { /* ... */ }, { deep: true })
情况五:监视多个数据
ts
watch([() => person.name, person.car], (newVal, oldVal) => {
console.log('多个数据变化了', newVal, oldVal)
}, { deep: true })
3.10 watchEffect --- 自动依赖收集
ts
import { ref, watchEffect } from 'vue'
let temp = ref(0)
let height = ref(0)
// 自动追踪函数内使用的响应式数据
const stop = watchEffect(() => {
if (temp.value >= 50 || height.value >= 20) {
console.log('联系服务器')
}
})
watch vs watchEffect:
| 维度 | watch |
watchEffect |
|---|---|---|
| 指定数据 | ✅ 明确指定 | ❌ 自动收集 |
| 获取旧值 | ✅ 可以 | ❌ 不可以 |
| 时机 | 惰性执行 | 立即执行 |
3.11 标签的 ref 属性
ts
// 用在普通 DOM 上 → 获取 DOM 节点
let title1 = ref()
// <h1 ref="title1">标题</h1>
console.log(title1.value) // <h1>元素</h1>
// 用在组件上 → 获取组件实例(需 defineExpose)
let ren = ref()
// <Person ref="ren" />
console.log(ren.value.name) // 子组件需 defineExpose({ name })
3.12 props(TypeScript 版)
ts
// 定义类型
export interface PersonInter {
id: string
name: string
age: number
}
export type Persons = Array<PersonInter>
// 子组件接收 props(三种写法)
// 1. 仅接收
const props = defineProps(['list'])
// 2. 接收 + 限制类型
defineProps<{ list: Persons }>()
// 3. 接收 + 限制类型 + 默认值 + 必要性
let props = withDefaults(defineProps<{ list?: Persons }>(), {
list: () => [{ id: '01', name: '默认', age: 18 }]
})
3.13 生命周期
Vue2 Vue3
─────────────────────────────────────────
beforeCreate → setup()
created → setup()
beforeMount → onBeforeMount()
mounted → onMounted() ⭐ 常用
beforeUpdate → onBeforeUpdate()
updated → onUpdated() ⭐ 常用
beforeDestroy → onBeforeUnmount() ⭐ 常用
destroyed → onUnmounted()
💡 思考 :Vue3 将
beforeCreate和created合并到了setup中,因为 setup 本身就在这两个钩子之间执行。这减少了钩子数量,让初始化逻辑更集中。
3.14 自定义 Hook
本质:把 setup 中的 Composition API 封装成可复用的函数,类似 Vue2 的 mixin
ts
// hooks/useSum.ts
import { ref, onMounted } from 'vue'
export default function () {
let sum = ref(0)
const increment = () => { sum.value += 1 }
const decrement = () => { sum.value -= 1 }
onMounted(() => { increment() })
return { sum, increment, decrement }
}
vue
<!-- 组件中使用 -->
<script setup lang="ts">
import useSum from './hooks/useSum'
import useDog from './hooks/useDog'
let { sum, increment, decrement } = useSum()
let { dogList, getDog } = useDog()
</script>
💡 创意用法:
useLocalStorage--- 响应式 localStorage 封装useMousePosition--- 实时追踪鼠标坐标useDebounce--- 防抖值封装useFetch--- 带加载状态的请求封装useDarkMode--- 暗黑模式切换
四、Vue Router 路由
4.1 基本配置
ts
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/pages/Home.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
})
export default router
ts
// main.ts
import router from './router/index'
app.use(router)
4.2 路由器工作模式
| 模式 | API | 优点 | 缺点 |
|---|---|---|---|
| History | createWebHistory() |
URL 美观(无 #) | 需服务端配合,刷新可能 404 |
| Hash | createWebHashHistory() |
兼容性好,无需服务端配置 | URL 带 #,SEO 不友好 |
4.3 嵌套路由
ts
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{ name: 'xiang', path: 'detail', component: Detail }
]
}
4.4 路由传参
query 参数
vue
<!-- 传递 -->
<RouterLink :to="{
path: '/news/detail',
query: { id: news.id, title: news.title }
}">{{ news.title }}</RouterLink>
<!-- 接收 -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.query.id, route.query.title)
</script>
params 参数
vue
<!-- 传递(对象写法必须用 name) -->
<RouterLink :to="{
name: 'xiang',
params: { id: news.id, title: news.title }
}">{{ news.title }}</RouterLink>
<!-- 接收 -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id, route.params.title)
</script>
⚠️ 使用 params 时需提前在路由规则中占位:
path: 'detail/:id/:title'
4.5 路由 props 配置
ts
{
name: 'xiang',
path: 'detail/:id/:title',
component: Detail,
// 三种写法:
props: { a: 1, b: 2 }, // 对象写法:静态 props
// props: true, // 布尔写法:params → props
props(route) { return route.query } // 函数写法:query → props
}
4.6 编程式导航
ts
import { useRoute, useRouter } from 'vue-router'
const route = useRoute() // 路由信息(替代 $route)
const router = useRouter() // 路由器(替代 $router)
router.push('/home') // 追加历史记录
router.replace('/about') // 替换当前记录
4.7 其他要点
| 功能 | 说明 |
|---|---|
| 命名路由 | name: 'zhuye',跳转时用 { name: 'zhuye' } |
| replace 属性 | <RouterLink replace> 替换而非追加历史记录 |
| 重定向 | { path: '/', redirect: '/about' } |
💡 思考 :路由的
props: true配置是 "解耦路由参数与组件" 的最佳实践,让组件可以通过 props 接收参数,而非直接依赖useRoute(),提高了组件的可复用性和可测试性。
五、Pinia 状态管理
Vue3 官方推荐的状态管理库,替代 Vuex
5.1 搭建环境
bash
npm install pinia
ts
// main.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
5.2 核心概念
Pinia Store 三要素:
┌────────────────────────────────────────┐
│ State ≈ data (状态) │
│ Getters ≈ computed (计算) │
│ Actions ≈ methods (动作) │
└────────────────────────────────────────┘
5.3 定义 Store(选项式写法)
ts
// store/count.ts
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
state() {
return { sum: 6, school: 'jdfs' }
},
actions: {
increment(value: number) {
if (this.sum < 10) this.sum += value
},
decrement(value: number) {
if (this.sum > 1) this.sum -= value
}
},
getters: {
bigSum: (state): number => state.sum * 10,
upperSchool(): string {
return this.school.toUpperCase()
}
}
})
5.4 修改数据(三种方式)
ts
const countStore = useCountStore()
// 方式一:直接修改
countStore.sum = 666
// 方式二:批量修改
countStore.$patch({ sum: 999, school: 'jdfs' })
// 方式三:通过 action 修改(可包含业务逻辑)
countStore.increment(5)
5.5 storeToRefs
ts
import { storeToRefs } from 'pinia'
const countStore = useCountStore()
const { sum, school, bigSum } = storeToRefs(countStore)
const { increment, decrement } = countStore // actions 不需要 storeToRefs
⚠️ 重要 :
storeToRefs只转换数据,Vue的toRefs会转换所有内容(包括 actions)。
5.6 $subscribe --- 侦听 state 变化
ts
talkStore.$subscribe((mutate, state) => {
console.log('talkList 变化了', mutate, state)
localStorage.setItem('talkList', JSON.stringify(state))
})
5.7 组合式写法
ts
import { defineStore } from 'pinia'
import { reactive } from 'vue'
import axios from 'axios'
import { nanoid } from 'nanoid'
export const useTalkStore = defineStore('talk', () => {
// state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
// action
async function getATalk() {
let { data: { content: title } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = { id: nanoid(), title }
talkList.unshift(obj)
}
return { talkList, getATalk }
})
💡 创意用法:
- 持久化 Store :配合
$subscribe+localStorage实现数据持久化- Store 插件:编写 Pinia 插件实现全局错误处理、日志记录
- SSR 支持:Pinia 天然支持 SSR,可在服务端渲染时安全使用
六、组件通信
Vue3 组件通信方式全景图:
┌──────────────┐
│ 父 ↔ 子 │
│ props │
│ 自定义事件 │
│ v-model │
└──────┬───────┘
│
┌────────────┼────────────┐
│ │ │
┌────────┴───┐ ┌────┴────┐ ┌────┴────────┐
│ 父 → 子 │ │ 子 → 父 │ │ 任意组件 │
│ $refs │ │ $parent │ │ mitt │
└────────────┘ └─────────┘ │ pinia │
┌────────────┐ ┌──────────┐ └─────────────┘
│ 祖 → 孙 │ │ 祖 → 孙 │
│ $attrs │ │ provide │
│ │ │ inject │
└────────────┘ └──────────┘
┌────────────────────────────┐
│ 父 → 子(内容分发) │
│ slot(插槽) │
└────────────────────────────┘
6.1 props
vue
<!-- 父组件 -->
<Child :car="car" :getToy="getToy" />
<!-- 子组件 -->
<script setup>
defineProps(['car', 'getToy'])
</script>
6.2 自定义事件
vue
<!-- 父组件:绑定自定义事件 -->
<Child @send-toy="toy = $event" />
<!-- 子组件:触发事件 -->
<script setup>
const emit = defineEmits(['send-toy'])
emit('send-toy', '奥特曼')
</script>
6.3 mitt --- 事件总线
ts
// utils/emitter.ts
import mitt from 'mitt'
const emitter = mitt()
export default emitter
ts
// 接收方
import emitter from '@/utils/emitter'
import { onUnmounted } from 'vue'
emitter.on('send-toy', (value) => { console.log(value) })
onUnmounted(() => { emitter.off('send-toy') })
// 发送方
import emitter from '@/utils/emitter'
emitter.emit('send-toy', toy.value)
6.4 v-model 组件通信
vue
<!-- 父组件 -->
<MyInput v-model="userName" />
<!-- 等价于 -->
<MyInput :modelValue="userName" @update:model-value="userName = $event" />
<!-- 子组件 -->
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:model-value'])
</script>
<template>
<input :value="modelValue" @input="emit('update:model-value', $event.target.value)">
</template>
💡 创意用法 :可以自定义
v-model的参数名实现多个双向绑定:
<MyInput v-model:username="name" v-model:password="pwd" />
6.5 $attrs --- 祖→孙透传
vue
<!-- 子组件:透传所有 attrs -->
<GrandChild v-bind="$attrs" />
$attrs自动排除 props 中已声明的属性,只传递"未消费"的属性。
6.6 refs 与 parent
| API | 方向 | 用途 |
|---|---|---|
$refs |
父→子 | 获取子组件实例或 DOM |
$parent |
子→父 | 获取父组件实例 |
6.7 provide / inject --- 祖孙通信
ts
// 祖先组件
provide('moneyContext', { money, updateMoney })
provide('car', car)
// 后代组件
let { money, updateMoney } = inject('moneyContext', { money: 0, updateMoney: () => {} })
let car = inject('car')
💡 思考 :
provide/inject是 Vue3 中实现 "依赖注入" 的核心机制,特别适合主题、国际化、权限等跨层级共享的场景。
6.8 slot --- 插槽
vue
<!-- 默认插槽 -->
<Category title="游戏">
<ul><li v-for="g in games">{{ g.name }}</li></ul>
</Category>
<!-- 具名插槽 -->
<Category>
<template v-slot:s1>内容1</template>
<template #s2>内容2</template> <!-- # 是 v-slot: 的缩写 -->
</Category>
<!-- 作用域插槽:子组件向父组件暴露数据 -->
<Game v-slot="params">
<ul><li v-for="g in params.games">{{ g.name }}</li></ul>
</Game>
💡 创意用法 :作用域插槽是 Vue 中 "控制反转" 的经典实现。子组件负责数据,父组件负责渲染结构,实现了数据与视图的解耦。可以用来构建高度可定制的表格、列表等组件。
七、其他 API
7.1 shallowRef 与 shallowReactive
ts
// 只跟踪顶层,不关心内部属性变化
let myVar = shallowRef(initialValue)
let myObj = shallowReactive({ ... })
🎯 适用场景 :大型对象/列表,只需监听整体替换,不需要深层响应。性能优化利器。
7.2 readonly 与 shallowReadonly
ts
const readOnlyCopy = readonly(original) // 深只读
const shallowCopy = shallowReadonly(original) // 浅只读(顶层只读)
🎯 适用场景:保护全局状态/配置不被意外修改。
7.3 toRaw 与 markRaw
ts
let rawPerson = toRaw(person) // 获取原始对象(非响应式)
let citys = markRaw([...]) // 标记对象永不转为响应式
let citys2 = reactive(citys) // 创建失败!
🎯 适用场景:
toRaw:将响应式对象传给非 Vue 库时使用markRaw:防止 Mock.js 数据被转为响应式(避免性能浪费)
7.4 customRef --- 自定义 ref
ts
import { customRef } from 'vue'
export default function (initValue: string, delay: number) {
let msg = customRef((track, trigger) => {
let timer: number
return {
get() {
track() // 告诉 Vue 追踪这个数据
return initValue
},
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() // 通知 Vue 数据变化了
}, delay)
}
}
})
return { msg }
}
💡 创意用法 :
customRef可以实现防抖输入、节流输入、带验证的输入等高级功能。
八、Vue3 新组件
8.1 Teleport --- 传送门
vue
<teleport to='body'>
<div class="modal" v-show="isShow">
<h2>弹窗</h2>
<button @click="isShow = false">关闭</button>
</div>
</teleport>
💡 思考 :Teleport 解决了 "组件逻辑属于当前组件,但 DOM 结构需要在 body 下" 的矛盾,如模态框、通知、Tooltip 等。
8.2 Suspense --- 异步组件加载
vue
<Suspense>
<template #default>
<AsyncChild />
</template>
<template #fallback>
<h3>加载中......</h3>
</template>
</Suspense>
ts
import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(() => import('./Child.vue'))
8.3 全局 API 转移到应用对象
Vue2 Vue3
─────────────────────────────────────
Vue.component() → app.component()
Vue.directive() → app.directive()
Vue.use() → app.use()
Vue.mixin() → app.mixin()
new Vue() → createApp()
8.4 其他破坏性变更
| 变更 | 说明 |
|---|---|
| 过渡类名 | v-enter → v-enter-from |
v-model |
替代 v-bind.sync |
v-if / v-for 优先级 |
v-if 优先级更高 |
| 移除 | $on、$off、$once、$children、filters |
九、💡 思考与创意用法
🧠 核心设计思想
- 组合优于继承:Composition API 通过函数组合实现逻辑复用,比 Mixin 更清晰
- 响应式是核心:Proxy 带来的深层响应式,让数据驱动视图更加自然
- 渐进式增强:Vue3 完全兼容 Vue2,可以逐步迁移
🚀 实战创意用法
1. 自定义 Hook 库
useDarkMode --- 暗黑模式,自动读取系统偏好
useLocalStorage --- 响应式 localStorage
useMouse --- 实时鼠标位置追踪
useDebounce --- 防抖值
useFetch --- 带加载/错误状态的请求封装
useIntersection --- 元素可见性检测(懒加载)
useClipboard --- 剪贴板读写
useWebSocket --- WebSocket 连接管理
2. Pinia 持久化插件
ts
// 利用 $subscribe + localStorage 实现数据持久化
// 也可以使用 pinia-plugin-persistedstate 插件
store.$subscribe((_, state) => {
localStorage.setItem(store.$id, JSON.stringify(state))
})
3. 组件设计模式
| 模式 | 说明 | 示例 |
|---|---|---|
| 无渲染组件 | 只提供逻辑,通过插槽渲染 | <RenderlessForm> |
| 受控组件 | v-model 双向绑定 | <CustomInput v-model="value"> |
| 提供/注入 | 跨层级状态共享 | 主题系统、权限控制 |
| 作用域插槽 | 数据与视图解耦 | 可定制表格、列表 |
4. 性能优化技巧
✅ 大型列表 → shallowRef / shallowReactive(避免深层响应开销)
✅ 只读数据 → readonly(防止意外修改 + 性能提示)
✅ 第三方对象 → markRaw(避免不必要的响应式转换)
✅ 防抖输入 → customRef(减少频繁更新)
✅ 异步组件 → defineAsyncComponent + Suspense(按需加载)
✅ 路由懒加载 → () => import('xxx.vue')(减小首屏包体积)
5. TypeScript 最佳实践
ts
// 1. 为 props 定义接口
interface Props {
title: string
count?: number
items: Array<{ id: string; name: string }>
}
// 2. 为 emit 定义类型
const emit = defineEmits<{
change: [value: string]
update: [id: string, data: object]
}>()
// 3. 为 store 定义类型
interface UserState {
name: string
age: number
}
export const useUserStore = defineStore('user', () => {
const user = ref<UserState>({ name: '', age: 0 })
return { user }
})
📋 Vue2 → Vue3 迁移清单
-
new Vue()→createApp() -
data选项改为函数 -
$on/$off/$once→mitt -
Vuex→Pinia -
v-bind.sync→v-model:xxx - 过滤器
{``{ msg | capitalize }}→ 计算属性或方法 -
$children→refs或provide/inject - 事件总线 →
mitt -
v-enter→v-enter-from
📝 总结 :Vue3 的核心升级在于 Composition API 带来的逻辑组织革命、Proxy 带来的响应式性能飞跃、以及 Pinia 带来的更简洁的状态管理。掌握这些核心概念,配合 TypeScript 和自定义 Hook,可以构建出高质量、可维护的 Vue3 应用。