1.1 为什么需要 Vue 3?
Vue 2 自 2016 年发布以来,在全球范围内积累了大量用户。然而随着应用规模的扩大,Options API 的局限性也逐渐暴露。Vue 3 在 2020 年 9 月正式发布,是对整个框架的彻底重写,核心目标是:
- 更好的 TypeScript 支持(Vue 2 对 TS 的支持十分有限)
- 解决大型组件中的逻辑碎片化问题
- 提升运行时性能(更快的虚拟 DOM,基于 Proxy 的响应式)
- 更小的打包体积(支持 Tree-shaking)
- 更好的代码复用机制(取代 Mixins)
本教程基于 Vue 3.4+(代号 Slam Dunk)编写,充分利用最新的 defineModel()、编译器宏改进等特性。建议使用 Vite 5+ 作为构建工具。
1.2 Options API 的问题:逻辑碎片化
在 Vue 2 的 Options API 中,代码按照选项类型组织——数据放 data(),计算属性放 computed,方法放 methods,生命周期分散在各个钩子。当一个组件变得复杂时,同一功能(比如"搜索过滤")的相关代码会被拆散到多个选项中:
// Vue 2 写法 — 搜索逻辑被拆散到多处
export default {
data() {
return {
searchQuery: '', // 搜索相关
items: [], // 搜索相关
isLoading: false, // 搜索相关
userName: '', // 用户相关(混在一起)
}
},
computed: {
filteredItems() { // 搜索相关
return this.items.filter(i => i.includes(this.searchQuery))
},
greeting() { // 用户相关
return `你好,${this.userName}`
}
},
methods: {
async fetchItems() { // 搜索相关
this.isLoading = true
this.items = await api()
this.isLoading = false
}
},
mounted() {
this.fetchItems() // 搜索相关
}
}
这种结构导致阅读和维护一个功能时,需要不断在不同区域来回跳转。Vue 3 的 Composition API 让相同逻辑的代码聚合在一起:
// Vue 3 写法 — 搜索逻辑集中,用户逻辑集中
import { ref, computed, onMounted } from 'vue'
// ── 搜索逻辑(可提取为 useSearch composable)──
const searchQuery = ref('')
const items = ref([])
const isLoading = ref(false)
const filteredItems = computed(() =>
items.value.filter(i => i.includes(searchQuery.value))
)
async function fetchItems() {
isLoading.value = true
items.value = await api()
isLoading.value = false
}
// ── 用户逻辑(清晰分离)──
const userName = ref('')
const greeting = computed(() => `你好,${userName.value}`)
onMounted(fetchItems)
1.3 setup() 函数:Composition API 的入口
setup() 是 Composition API 的执行入口,在组件实例创建之前调用,所有组合式逻辑都在这里定义。
<!-- 标准 setup() 写法(非语法糖版本,适合理解原理)-->
<script>
import { ref, computed } from 'vue'
export default {
name: 'Counter',
props: { initialCount: Number },
setup(props, { emit }) {
const count = ref(props.initialCount ?? 0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
emit('change', count.value)
}
return { count, doubled, increment } // 暴露给模板
}
}
</script>
<template>
<button @click="increment">{{ count }} (× 2 = {{ doubled }})</button>
</template>
1.4 <script setup>:编译器语法糖
<script setup> 是 Vue 3.2 引入的编译器语法糖,是目前官方推荐的单文件组件写法。它让代码更简洁:
- 顶层变量和函数自动暴露给模板,无需
return defineProps、defineEmits等编译器宏直接可用,无需导入- 更好的 TypeScript 类型推断
- 更小的运行时开销
<script setup lang="ts">
import { ref, computed } from 'vue'
// defineProps 是编译器宏,无需 import
const props = defineProps<{
initialCount?: number
}>()
// defineEmits 同理
const emit = defineEmits<{
change: [value: number]
}>()
// 所有顶层声明自动暴露给模板
const count = ref(props.initialCount ?? 0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
emit('change', count.value)
}
</script>
<template>
<button @click="increment">
{{ count }} (× 2 = {{ doubled }})
</button>
</template>
1.5 Vue 3 与 Vue 2 核心差异对照
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式实现 | Object.defineProperty(有缺陷) | ES6 Proxy(全面、高效) |
| 逻辑复用 | Mixins(命名冲突、来源不清晰) | Composables(明确的来源与命名) |
| TypeScript | 类型支持差,需要 vue-class-component | 原生 TS 支持,script setup 完美推断 |
| 根节点 | 只能有一个根元素 | 支持 Fragment(多根节点) |
| 全局 API | Vue.use() / Vue.component()(全局污染) | createApp() 实例隔离 |
| v-model | 只能一个,prop 固定为 value | 支持多个 v-model,可自定义 prop 名 |
| Teleport | 无 | 内置 <Teleport> 组件 |
| Suspense | 无 | 内置 <Suspense> 异步组件支持 |
| 响应式 API | this.$set / this.$delete(补丁方案) | 天然支持属性新增/删除 |
1.6 与 React Hooks 的对比
Composition API 受到 React Hooks 启发,但有本质差异:
React Hooks 限制
- 必须在函数组件顶层调用(不能在条件/循环中)
- 每次渲染都重新执行(性能开销)
- 闭包陷阱(stale closure)
- 需要手动管理依赖数组(useEffect deps)
Vue Composition API 优势
- setup() 只执行一次,无重复执行问题
- 响应式系统自动追踪依赖,无需手动声明
- 无闭包陷阱(ref.value 始终最新)
- 可以在条件语句中使用(无 Rules of Hooks)
// React — useEffect 需要手动写依赖数组,容易遗漏
useEffect(() => {
fetchData(userId) // 如果忘写 userId 会有 bug
}, [userId]) // 手动维护依赖数组
// Vue — watchEffect 自动追踪,不会遗漏
watchEffect(() => {
fetchData(userId.value) // 自动追踪 userId,无需声明
})
1.7 创建第一个 Vue 3 项目
使用官方脚手架工具快速创建项目:
# 使用 Vite 官方模板创建 Vue 3 + TypeScript 项目
npm create vue@latest my-app
# 交互式选择:TypeScript ✓ JSX ✗ Router ✓ Pinia ✓ Vitest ✓
cd my-app
npm install
npm run dev # 启动开发服务器 http://localhost:5173
src/main.ts 中使用 createApp(App).mount('#app') 替代了 Vue 2 的 new Vue()。每个 createApp 实例都是独立的,互不影响,非常适合在同一页面挂载多个 Vue 应用。
1.8 名词速查
Vue 3 移除了 Vue.filter()、Vue.prototype、$listeners、$scopedSlots 等。v-for 和 v-if 的优先级也发生了变化(Vue 3 中 v-if 优先级更高)。迁移前建议阅读官方迁移指南。