3197 字
16 分钟
Vue 2 到 Vue 3 详细衔接教程(小白也能看懂)

Vue 2 到 Vue 3 详细衔接教程(小白也能看懂)#

如果你学过 Vue 2,现在想升级到 Vue 3——或者你看到的教程有 Vue 2 有 Vue 3,分不清该学哪个——这一篇就是为你准备的。

Vue 3 是 Vue 官方于 2020 年发布的重大版本,现在是 Vue 的绝对主流,新项目几乎全用 Vue 3。但 Vue 3 的写法变化很大,很多 Vue 2 老手都会绕晕。这一篇用逐条对比 + 可运行示例,带你把 Vue 2 和 Vue 3 的差异全部理清楚。

提醒:如果你还没学过 Vue,建议先看前面那篇《Vue / React 框架入门》,再回来看这篇衔接教程。


一、Vue 3 到底改了什么?一张总览图#

Vue 2 → Vue 3 的变化主要分四块:

层面Vue 2Vue 3
响应式原理Object.defineProperty(有局限)Proxy(更强大)
组合代码Options API(唯一)Composition API(新增)+ Options API 共存
性能较好更快、更小(Tree-shaking)
生态Vuex、Vue Router 3Pinia、Vue Router 4
原生支持不支持 TS原生 TS 支持

核心变化一句话: Vue 3 用更现代的 Proxy 重写了底层,同时新增了 Composition API(组合式)这种更灵活的写法,但 Options API(选项式)在 Vue 3 里依然能用——这是很多人容易忽略的关键点。

最让你安心的消息: 你会的 Vue 2 写法,在 Vue 3 里大部分依然能跑。Vue 3 并没有推翻一切,而是”加新 + 优化”。


二、Composition API vs Options API:两种写法#

这是 Vue 2 → Vue 3 最核心的区别。先看懂这两种 API。

Options API:Vue 2 的传统写法#

Vue 2 只能这样写——把代码按”选项”(data、methods、computed…)分类:

<template>
<button @click="add">点击 {{ count }}</button>
</template>
<script>
export default {
// data 选项:数据
data() {
return { count: 0 };
},
// methods 选项:方法
methods: {
add() {
this.count++;
}
},
// computed 选项:计算属性
computed: {
double() {
return this.count * 2;
}
},
// watch 选项:监听
watch: {
count(newVal) {
console.log("count 变了:", newVal);
}
}
};
</script>

特点:按”功能类型”分块(数据放一起、方法放一起)。优点是有规矩;缺点是当一个组件功能多时,相关代码被拆散在不同选项里,不好维护。

Composition API:Vue 3 新增写法#

把”同一个功能相关的代码”放在一起

<template>
<button @click="add">点击 {{ count }}</button>
</template>
<script setup>
import { ref, computed, watch } from "vue";
// 所有和 count 相关的代码都在一起
const count = ref(0);
function add() {
count.value++;
}
const double = computed(() => count.value * 2);
watch(count, (newVal) => {
console.log("count 变了:", newVal);
});
</script>

特点:按”业务逻辑”组织,相关代码聚在一起,更容易维护和复用。而且 <script setup> 语法让代码更简洁——你不用写 export default,也不用 return

两种写法怎么选?#

Options APIComposition API
适合简单组件、Vue 2 老手过渡复杂组件、需要复用逻辑
学习成本略高
代码组织按类型分块按功能聚合
复用逻辑用 mixin(有坑)用组合式函数(推荐)

官方建议: Vue 3 新项目首选 Composition API(<script setup>。但 Options API 依然合法,你想用哪个都行。


三、响应式数据:data → ref / reactive#

这是最容易混淆的地方。

Vue 2 的 data#

export default {
data() {
return {
count: 0,
user: { name: "小明", age: 18 }
};
}
};

Vue 3 的 ref:单个值#

ref 用来包装单个基本值(数字、字符串、布尔):

<script setup>
import { ref } from "vue";
const count = ref(0);
const name = ref("小明");
// 模板里直接用(自动解开)
// JS 代码里要 .value
function add() {
count.value++;
}
</script>
<template>
<p>{{ count }} - {{ name }}</p>
</template>

关键坑: 模板里 count 直接用,但 JS 里必须写 count.value

Vue 3 的 reactive:对象#

reactive 用来包装对象

<script setup>
import { reactive } from "vue";
const user = reactive({
name: "小明",
age: 18
});
// 对象属性不用 .value,直接改
function birthday() {
user.age++;
}
</script>
<template>
<p>{{ user.name }} - {{ user.age }}</p>
</template>

ref vs reactive 怎么选?#

一个简单建议: 基本类型和简单的值用 ref;对象用 reactive。但要注意——ref 也可以包对象,而且现在的写法越来越倾向于统一用 ref(更一致)。

refreactive
包什么基本值 / 对象都可以只能包对象
JS 里访问.value直接 .属性
模板里自动解开直接用

小技巧: 不确定用哪个就都用 ref,出错少。

const user = ref({ name: "小明" });
console.log(user.value.name); // 小明

四、computed 和 watch:写法变化#

computed(计算属性)#

Vue 2:

computed: {
double() {
return this.count * 2;
}
}
// 使用:this.double

Vue 3(Composition):

<script setup>
import { ref, computed } from "vue";
const count = ref(2);
const double = computed(() => count.value * 2);
// 模板里直接用 double
</script>

watch(监听)#

Vue 2:

watch: {
count(newVal, oldVal) {
console.log("变了", newVal, oldVal);
}
}

Vue 3(Composition):

<script setup>
import { ref, watch } from "vue";
const count = ref(0);
watch(count, (newVal, oldVal) => {
console.log("变了", newVal, oldVal);
});

注意: Vue 3 的 watch 第一个参数是 ref 对象(不用写 .value),第二个是回调。

监听对象属性要这样:

<script setup>
import { reactive, watch } from "vue";
const user = reactive({ name: "小明" });
watch(() => user.name, (newVal) => {
console.log("名字变了:", newVal);
});
</script>

五、生命周期:改名 + 变成函数#

Vue 的生命周期(组件从创建到销毁的过程)在 Vue 3 里基本同名,但 Options 写法能对应,Composition 写法变成函数了。

对比表#

Vue 2 (Options)Vue 3 (Options)Vue 3 (Composition)
beforeCreatebeforeCreate(不需要,直接写在 setup)
createdcreated(同上)
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroybeforeUnmountonBeforeUnmount
destroyedunmountedonUnmounted

两个改名: beforeDestroybeforeUnmountdestroyedunmounted

Vue 2 写法#

export default {
created() {
console.log("组件创建");
},
mounted() {
console.log("已挂载");
},
destroyed() {
console.log("销毁了");
}
};

Vue 3 Composition 写法#

<script setup>
import { onMounted, onUnmounted } from "vue";
// created/mounted 之前的内容直接写在这里
console.log("组件创建");
onMounted(() => {
console.log("已挂载");
});
onUnmounted(() => {
console.log("销毁了");
});
</script>

关键:<script setup> 里,没有 created/beforeCreate 对应的函数——因为 setup 本身就发生在它们之前,直接在 setup 里写代码就相当于 created 时执行。


六、v-model:绑定语法变化#

Vue 2 的 v-model#

<!-- 相当于 :value + @input -->
<input v-model="name">

Vue 2 里 v-model 默认用 value 属性和 input 事件。

Vue 3 的 v-model:更灵活#

Vue 3 里 v-model 默认改用 modelValue 属性和 update:modelValue 事件:

<!-- Vue 3 单个 -->
<input v-model="name">

最重要的新特性:一个组件可以用多个 v-model! Vue 2 只能一个。

<my-form v-model="name" v-model:email="email" />

自定义组件里怎么实现 v-model#

Vue 2:

export default {
props: ["value"],
methods: {
update() {
this.$emit("input", newVal);
}
}
};

Vue 3:

<script setup>
defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
// 触发更新:emit("update:modelValue", newVal)
</script>

七、过滤器 filters:被移除了!#

Vue 2 有 filters(过滤器),用来格式化文本:

<!-- Vue 2 -->
<p>{{ price | currency }}</p>
// Vue 2
filters: {
currency(v) {
return "¥" + v;
}
}

Vue 3 移除了 filters,官方建议用方法计算属性代替:

<!-- Vue 3:改用方法 -->
<p>{{ currency(price) }}</p>
<script setup>
function currency(v) {
return "¥" + v;
}
</script>

如果你从 Vue 2 迁移,记得把所有 filters 改成方法或 computed。


八、全局 API 变化:Vue.xxx 改成了导入#

Vue 2 里很多全局 API 挂载在 Vue 上:

// Vue 2
Vue.component("my-comp", { ... }); // 注册全局组件
Vue.directive("focus", { ... }); // 注册全局指令
Vue.mixin({ ... }); // 全局混入

Vue 3 里这些改成了从 vue 导入的独立函数:

// Vue 3
import { createApp } from "vue";
const app = createApp(App);
app.component("my-comp", { ... });
app.directive("focus", { ... });
app.mixin({ ... });
app.mount("#app");

最大的变化是 new Vue()createApp()

// Vue 2
new Vue({ render: h => h(App) }).$mount("#app");
// Vue 3
import { createApp } from "vue";
createApp(App).mount("#app");

九、事件 API:on/on / off 被移除#

Vue 2 里可以用 $on$emit$off 做组件间通信(事件总线 EventBus):

// Vue 2 事件总线
Vue.prototype.$bus = new Vue();
this.$bus.$on("event", callback);
this.$bus.$emit("event", data);

Vue 3 移除了 $on$off$once$emit 还在)。官方推荐替代方案:

  • 简单场景用 props / emit
  • 复杂场景用 Pinia(状态管理)mitt(轻量事件库)

十、v-for 的 key:规则更严#

Vue 2 里 v-forkey 建议写但不强制。Vue 3 里 key必需的,否则会有警告。

<!-- Vue 3 必须写 key -->
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>

key 用什么? 用能唯一标识该项的值(比如 id),不要用 index(尤其有增删、排序时,用 index 会出 bug)。


十一、样式和模板细节变化#

多个根节点#

Vue 2 的模板只能有一个根元素(最外层只能包一个 div):

<!-- Vue 2:报错,只能一个根 -->
<template>
<div>...</div>
</template>

Vue 3 支持多个根节点(Fragment 片段):

<!-- Vue 3:可以多个根,不再强制包一层 div -->
<template>
<header>标题</header>
<main>内容</main>
</template>

这让模板更干净,少套很多无用 div。

组件事件区分#

Vue 3 里,组件上写的 @click 默认是自定义事件,而不是原生 DOM 事件。要监听原生事件需加 .native(Vue 3 已移除 .native,改用在组件内部 emits 声明,或直接用 @click 但组件要透传)。这是迁移时容易踩的坑。


十二、状态管理:Vuex → Pinia#

Vue 2 用 Vuex#

store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) { state.count++; }
},
actions: {
doIncrement({ commit }) { commit("increment"); }
}
});

Vuex 有 state、mutations、actions、getters 四件套,比较繁琐

Vue 3 用 Pinia(官方推荐)#

Pinia 是 Vue 3 的官方状态管理库,更简单、支持 TS、移除了 mutations(直接用函数修改):

store/counter.js
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++;
}
}
});

组件里用:

<script setup>
import { useCounterStore } from "../store/counter";
const store = useCounterStore();
</script>
<template>
<p>{{ store.count }}</p>
<button @click="store.increment">+1</button>
</template>

Pinia 相比 Vuex 的好处: 没有 mutations、getters 更简单、原生支持 Composition API、自动支持 TypeScript。新项目直接用 Pinia。


十三、路由:Vue Router 3 → 4#

Vue 2 用 Vue Router 3,Vue 3 用 Vue Router 4。基础用法差不多,几个小变化:

安装#

Terminal window
# Vue 3 用 router 4
npm install vue-router@4

基本配置对比#

Vue 2:

import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const router = new VueRouter({
routes: [...]
});

Vue 3:

import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(), // 用 createWebHistory 代替 mode: "history"
routes: [...]
});

路由守卫变化不大#

beforeEach 等守卫基本一样,只是 next() 变成可选(可以直接 return)。

// Vue 3
router.beforeEach((to, from) => {
if (to.path === "/admin" && !isLogin) {
return "/login"; // 直接 return 跳转,不用 next
}
});

十四、从 Vue 2 迁移到 Vue 3 的完整步骤#

如果你有个现成的 Vue 2 项目要升级,按这个顺序:

1. 先升级依赖#

Terminal window
npm install vue@3
npm install vue-router@4
npm install pinia # 替代 vuex

2. 改入口文件#

// main.js(Vue 2 → Vue 3)
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
const app = createApp(App);
app.use(router);
app.use(createPinia());
app.mount("#app");

3. 清理 Vue 2 特有写法#

  • filters → 改成方法/computed
  • $on/$off → 改用 props/emit 或 Pinia
  • Vue.componentapp.component
  • new Vue()createApp()

4. 更新生命周期名#

  • beforeDestroybeforeUnmount
  • destroyedunmounted

5. 逐步把 data/methods 改成 ref/composition#

不强制一次性全改,可以混合过渡——Options 和 Composition 能共存。

6. 用官方工具检查#

Vue 官方提供了 Vue Migration Helper(迁移助手)工具,能自动扫描代码里需要改的地方,强烈推荐用。


十五、一个完整的 Vue 3 项目示例#

把学到的串起来,写一个 Vue 3 计数器组件(完整、可运行):

<template>
<div class="counter">
<h2>Vue 3 计数器</h2>
<p>当前值:{{ count }}</p>
<p>翻倍:{{ double }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">重置</button>
<p v-if="count > 10" class="tip">你已经超过 10 啦 🎉</p>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
// 响应式数据
const count = ref(0);
// 方法
function increment() { count.value++; }
function decrement() { count.value--; }
function reset() { count.value = 0; }
// 计算属性
const double = computed(() => count.value * 2);
// 监听
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变成 ${newVal}`);
});
</script>
<style scoped>
.counter { text-align: center; font-family: sans-serif; }
button {
margin: 4px; padding: 8px 16px;
border: none; border-radius: 6px;
background: #42b883; color: white; cursor: pointer;
}
.tip { color: #e74c3c; }
</style>

看懂这段就掌握了 Vue 3 核心: ref(数据)、函数(方法)、computed(计算属性)、watch(监听)、v-if(条件)、模板插值。


十六、总结#

这一篇我们详细对比了 Vue 2 和 Vue 3:

  • 总览:Proxy 重写、Composition API 新增、性能更好、生态更新
  • 两种 API:Options(按类型分块)vs Composition(按功能聚合),<script setup> 更简洁
  • 响应式dataref / reactiveref.value
  • computed / watch:变成导入的函数
  • 生命周期:改名(beforeUnmount/unmounted)+ 变函数
  • v-model:支持多个,默认 modelValue
  • filter 移除:改用方法/computed
  • 全局 APInew Vue()createApp()
  • 事件 API$on/$off 移除
  • v-for key:必填
  • 多根节点:Vue 3 支持 Fragment
  • Vuex → Pinia:更简单、支持 TS
  • Router 3 → 4:createRouter/createWebHistory
  • 迁移步骤:改入口、清旧语法、改生命周期、用迁移助手

最后给你一个安心的结论: Vue 3 不是从零重来,而是在 Vue 2 的思路上优化和扩展。你会的 Vue 2 知识没有白学——v-ifv-forv-model、组件、props、emit 这些核心概念完全一样。你只需要补上 Composition API 和那几处改名/移除的地方,就能平稳过渡到 Vue 3。

下一步: 把上面那个完整计数器示例自己敲一遍跑起来,再把你熟悉的 Vue 2 项目试着用 Vue 3 重写一遍。实践几次,你就完全上手 Vue 3 了。

Vue 2 到 Vue 3 详细衔接教程(小白也能看懂)
https://021028.xyz/posts/default/79/
作者
021028
发布于
2026-08-11
许可协议
CC BY-NC-SA 4.0