1. 核心语法结构对比
| Vue 2 (Options API) | Vue 3 (<script setup>) |
说明 |
|---|---|---|
| 模板 | 模板 | 模板语法基本不变 (v-if, v-for, v-model) |
html<br><template><div>{``{ count }}</div></template><br><script><br>export default {<br> data() { return { count: 0 } },<br> methods: { inc() { this.count++ } }<br>}<br></script> |
html<br><template><div>{``{ count }}</div></template><br><script setup><br>import { ref } from 'vue'<br>const count = ref(0)<br>const inc = () => count.value++<br></script> |
<script setup> 是语法糖,无需 export default,顶层变量自动暴露给模板。 |
| this 指向 | 无 this | Vue 2 依赖 this;Vue 3 直接访问变量,更清晰。 |
2. 响应式数据 (Reactivity)
不再区分 data, props, computed 的写法,统一用函数。
| 概念 | Vue 2 | Vue 3 (Composition API) | 注意事项 |
|---|---|---|---|
| 基础类型 (string, number) | data() { return { count: 0 } } 使用:this.count |
const count = ref(0) 使用 (JS): count.value 使用 (模板): {``{ count }} |
重点: JS 中访问 ref 必须加 .value,模板中自动解包。 |
| 对象/数组 | data() { return { user: {} } } |
方式 A: const user = ref({}) (需 .value) 方式 B: const user = reactive({}) (无需 .value) |
推荐: 优先用 ref 保持一致性;若对象层级深且频繁修改,可用 reactive。 |
| 计算属性 | computed: { double() { return this.count * 2 } } |
const double = computed(() => count.value * 2) |
只读。若要写值:computed({ get: ()=>..., set: (v)=>... }) |
| 监听器 | watch: { count(val) { ... } } |
watch(count, (newVal, oldVal) => { ... }) 监听多个:watch([a, b], ...) |
watch 第一个参数可以是 ref 或 getter 函数 () => obj.prop。 |
| 深度监听 | watch: { user: { handler, deep: true } } |
watch(user, ..., { deep: true }) 若用 reactive 默认开启深度。 |
3. 生命周期 (Lifecycle Hooks)
钩子函数名称改变,需要导入 并在 setup 中调用。
| 阶段 | Vue 2 | Vue 3 (导入自 'vue') | 说明 |
|---|---|---|---|
| 创建前 | beforeCreate |
❌ 不需要 | setup() 本身就是初始化逻辑 |
| 创建后 | created |
❌ 不需要 | 在 setup() 中直接写代码即可 |
| 挂载前 | beforeMount |
onBeforeMount |
|
| 挂载后 | mounted |
onMounted |
常用:操作 DOM, 发起请求 |
| 更新前 | beforeUpdate |
onBeforeUpdate |
|
| 更新后 | updated |
onUpdated |
|
| 卸载前 | beforeDestroy |
onBeforeUnmount |
⚠️ 名字变了 (Destroy -> Unmount) |
| 卸载后 | destroyed |
onUnmounted |
⚠️ 名字变了 |
| 错误捕获 | errorCaptured |
onErrorCaptured |
示例:
javascript
// Vue 2
mounted() { console.log('mounted') }
// Vue 3
import { onMounted } from 'vue'
onMounted(() => {
console.log('mounted')
})
4. 组件通信 (Props & Emits)
| 功能 | Vue 2 | Vue 3 (<script setup>) |
说明 |
|---|---|---|---|
| 接收 Props | props: ['title'] 使用:this.title |
const props = defineProps(['title']) 使用:props.title |
defineProps 无需导入,编译器宏。 |
| 定义 Emits | emits: ['update'] 触发:this.$emit('update') |
const emit = defineEmits(['update']) 触发:emit('update') |
defineEmits 无需导入。 |
| 双向绑定 | v-model="val" (默认) |
v-model="val" (默认 prop: modelValue, event: update:modelValue) |
Vue 3 移除了 .sync,统一用 v-model:propName。 |
| 多 v-model | 不支持 (需用 .sync 变通) | <Comp v-model:title="t" v-model:count="c" /> |
父组件可绑定多个模型。 |
5. 插槽 (Slots) 与 refs
| 功能 | Vue 2 | Vue 3 (<script setup>) |
说明 |
|---|---|---|---|
| 默认插槽 | <slot></slot> |
<slot></slot> |
模板用法不变 |
| 具名插槽 | <slot name="header"></slot> |
<slot name="header"></slot> |
|
| 作用域插槽 | <template v-slot:default="slotProps"> |
<template #default="{ item }"> |
# 是 v-slot: 的简写 (Vue 2.6+ 已有,Vue 3 推荐) |
| 获取子组件实例 | this.$refs.child |
const child = ref(null) <Child ref="child" /> |
重大变化: ref 现在是响应式变量,需绑定到 template 的 ref 属性。访问时用 child.value。 |
| 获取 DOM 元素 | this.$refs.input |
const input = ref(null) <input ref="input" /> |
同上,统一用 ref()。 |
6. 全局 API 与 内置组件变化
| Vue 2 | Vue 3 | 说明 |
|---|---|---|
Vue.use(Plugin) |
app.use(Plugin) |
必须通过 createApp 实例调用 |
Vue.component(...) |
app.component(...) |
|
Vue.directive(...) |
app.directive(...) |
|
new Vue({ ... }) |
createApp({ ... }) |
入口变了 |
<transition> |
<transition> |
类名变化:v-enter -> v-enter-from, v-leave -> v-leave-from |
<transition-group> |
<transition-group> |
同上 |
v-on:click |
@click |
简写依旧支持 |
keyCode 修饰符 |
❌ 已移除 | 必须使用 key 别名 (如 .enter) 或检查 event.key |
$children |
❌ 已移除 | 使用 ref 或 provide/inject 替代 |
$listeners |
❌ 已移除 | 所有监听器都合并到 $attrs 中,自动透传 |
7. 状态管理 (Vuex -> Pinia)
Vue 3 官方推荐 Pinia 替代 Vuex。
| 概念 | Vuex (Vue 2) | Pinia (Vue 3) | 优势 |
|---|---|---|---|
| 定义 Store | new Vuex.Store({ state, mutations, actions }) |
defineStore('id', { state, actions, getters }) |
去掉了 mutations,只有 state/getters/actions。 |
| 读取 State | this.$store.state.count |
store.count |
像访问普通对象属性一样。 |
| 修改 State | this.$store.commit('inc') |
store.count++ 或 store.inc() |
直接修改或调用 action,更直观。 |
| 映射辅助函数 | mapState, mapActions |
不需要!直接解构 (需注意 storeToRefs) |
代码更少。 |
| TypeScript | 支持较差,需复杂泛型 | 原生完美支持 TS | 自动推断类型。 |
Pinia 示例:
javascript
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++ } // 直接用 this
}
})
// 组件中使用
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
store.increment()
console.log(store.count)
8. 常见迁移陷阱 (Pitfalls)
- Ref 的
.value:- 在
<script>里忘记加.value是最常见的错误。 - 口诀: "模板里直接用,脚本里加点值"。
- 在
- Reactive 的解构丢失响应性 :
const { name } = reactive(user)->name失去响应性。- 解决 : 使用
toRefs(user)或直接用user.name。
- Watch 的第一个参数 :
- 如果监听
reactive对象的某个属性,必须用 getter:watch(() => obj.prop, ...)。 - 如果直接传
obj.prop(值),它不会响应变化。
- 如果监听
- Provide / Inject :
- Vue 2:
provide: { key: value } - Vue 3:
provide('key', value)和inject('key', defaultValue)函数。
- Vue 2:
- 异步组件 :
- Vue 2:
() => import('./Comp.vue') - Vue 3:
defineAsyncComponent(() => import('./Comp.vue'))
- Vue 2:
9. 快速迁移步骤
- 升级依赖 :
npm install vue@latest - 全局搜索替换 :
beforeDestroy->onBeforeUnmountdestroyed->onUnmounted$children-> 重构为ref列表
- 转换单文件组件 (SFC) :
- 将
<script>改为<script setup lang="ts">(推荐直接上 TS)。 - 把
data转为ref/reactive。 - 把
methods转为普通函数。 - 把
computed转为computed()。 - 把
watch转为watch()。 - 把
props/emits转为defineProps/defineEmits。
- 将
- 状态管理: 逐步将 Vuex 模块迁移到 Pinia。
总结 : Vue 3 的核心思想是**"逻辑复用"** 。以前靠 Mixins (有命名冲突风险),现在靠 Composables (组合式函数) (如 useMouse, useFetch)。
这是 Vue 3 最强大的特性,建议在学习基础语法后,立即学习如何编写 Composables。