☰
vue2和vue3有什么区别,用法上有哪些区别
2026/11/6 1:34:29 网站建设 项目流程

Vue 2 和 Vue 3 在写法哲学上就有根本差异:Vue 2 是Options API(按 data / methods / computed 等选项分类写),Vue 3 主推Composition API +<script setup>(按业务逻辑聚合写),同时 Vue 3 也兼容 Options API。下面通过大量对照示例,把核心用法差异讲透。


一、创建应用实例

// Vue 2importVuefrom'vue'importAppfrom'./App.vue'newVue({render:h=>h(App)}).$mount('#app')
// Vue 3import{createApp}from'vue'importAppfrom'./App.vue'constapp=createApp(App)// 全局 API 从 Vue.xxx 改为 app.xxxapp.component('MyComp',MyComp)app.directive('focus',focusDirective)app.use(router)app.mount('#app')

关键变化:

  • new Vue()→createApp()
  • 全局 API(Vue.component、Vue.use、Vue.directive)改为app实例方法
  • Vue.prototype.$http = ...→app.config.globalProperties.$http = ...

二、组件写法:Options API vs Composition API

1. 计数器组件对照

Vue 2(Options API):

<template> <div> <p>计数:{{ count }}</p> <p>双倍:{{ double }}</p> <button @click="increment">增加</button> <button @click="reset">重置</button> </div> </template> <script> export default { data() { return { count: 0 } }, computed: { double() { return this.count * 2 } }, methods: { increment() { this.count++ }, reset() { this.count = 0 } }, mounted() { console.log('组件挂载完成', this.count) } } </script>

Vue 3(Composition API + setup()):

<template> <div> <p>计数:{{ count }}</p> <p>双倍:{{ double }}</p> <button @click="increment">增加</button> <button @click="reset">重置</button> </div> </template> <script> import { ref, computed, onMounted } from 'vue' export default { setup() { const count = ref(0) const double = computed(() => count.value * 2) const increment = () => { count.value++ } const reset = () => { count.value = 0 } onMounted(() => { console.log('组件挂载完成', count.value) }) return { count, double, increment, reset } } } </script>

Vue 3(<script setup>语法糖,最推荐):

<template> <div> <p>计数:{{ count }}</p> <p>双倍:{{ double }}</p> <button @click="increment">增加</button> <button @click="reset">重置</button> </div> </template> <script setup> import { ref, computed, onMounted } from 'vue' const count = ref(0) const double = computed(() => count.value * 2) const increment = () => { count.value++ } const reset = () => { count.value = 0 } onMounted(() => { console.log('组件挂载完成', count.value) }) </script>

💡<script setup>的优势:无需return,顶层变量/函数/导入的组件自动暴露给模板,代码量减少 30%+,TypeScript 支持更好。

2. 响应式数据:ref vs reactive

// Vue 2:所有响应式数据都写在 data() 里data(){return{count:0,user:{name:'张三'}}}// 动态添加属性需要 this.$setthis.$set(this.user,'age',28)
// Vue 3:Proxy 代理,直接赋值即响应import{ref,reactive}from'vue'// 基本类型用 ref,访问需 .valueconstcount=ref(0)count.value++// 修改console.log(count.value)// 读取// 对象/数组用 reactiveconstuser=reactive({name:'张三'})user.age=28// 直接生效,无需 $set// reactive 对象直接解构会丢失响应式,需用 toRefsconst{name,age}=toRefs(user)

⚠️新手最容易踩的坑:

  • ref在 JS 中必须写.value,模板中自动解包不需要
  • reactive不能直接解构,否则响应式丢失,要用toRefs()

3. 计算属性与侦听器

// Vue 2computed:{fullName(){returnthis.firstName+' '+this.lastName}},watch:{count(newVal,oldVal){console.log(`count 从${oldVal}变为${newVal}`)}}
// Vue 3import{computed,watch,watchEffect}from'vue'constfullName=computed(()=>firstName.value+' '+lastName.value)// 侦听单个源watch(count,(newVal,oldVal)=>{console.log(`count 从${oldVal}变为${newVal}`)})// 侦听多个源watch([count,name],([newCount,newName])=>{console.log(`count=${newCount}, name=${newName}`)})// watchEffect:自动收集依赖watchEffect(()=>{console.log(`count 变化时触发:${count.value}`)})

4. 生命周期钩子

Vue 2Vue 3 Options APIVue 3 Composition API
beforeCreatebeforeCreate-(setup替代)
createdcreated-(setup替代)
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroybeforeDestroyonBeforeUnmount
destroyeddestroyedonUnmounted
// Vue 3 <script setup>import{onMounted,onBeforeUnmount}from'vue'onMounted(()=>{console.log('挂载')})onBeforeUnmount(()=>{console.log('卸载前清理')})

⚠️生命周期名称变化:beforeDestroy→onBeforeUnmount,destroyed→onUnmounted。Vue 3 的生命周期钩子必须从 vue 显式导入。


三、组件通信

1. Props 与 Emits

Vue 2:

<!-- 子组件 --> <script> export default { props: { title: { type: String, required: true }, count: { type: Number, default: 0 } }, methods: { handleClick() { this.$emit('update', 123) } } } </script>

Vue 3(<script setup>):

<script setup> const props = defineProps({ title: { type: String, required: true }, count: { type: Number, default: 0 } }) const emit = defineEmits(['update']) const handleClick = () => { emit('update', 123) } // 使用 TS 类型声明(推荐) // const props = defineProps<{ // title: string // count?: number // }>() </script>

📌defineProps和defineEmits是编译器宏,在<script setup>中自动可用,无需导入。

2. v-model 变化(破坏性变更)

<!-- Vue 2:v-model 本质是 :value + @input --><Childv-model="value"/><!-- 等价于 --><Child:value="value"@input="value = $event"/><!-- 多值同步需要用 .sync 修饰符 --><Child:title.sync="title"/>
<!-- Vue 3:v-model 默认是 :modelValue + @update:modelValue --><Childv-model="value"/><!-- 等价于 --><Child:modelValue="value"@update:modelValue="value = $event"/><!-- 支持多个 v-model 绑定 --><Childv-model:title="title"v-model:content="content"/><!-- .sync 被移除,统一用 v-model:xxx -->

子组件接收:

// Vue 3 子组件constprops=defineProps(['modelValue','title'])constemit=defineEmits(['update:modelValue','update:title'])

3. 跨组件通信:provide / inject

// Vue 3 祖先组件import{provide,ref}from'vue'constcount=ref(0)provide('count',count)// 提供响应式数据provide('increment',()=>count.value++)
// Vue 3 后代组件import{inject}from'vue'constcount=inject('count',0)// 第二个参数是默认值constincrement=inject('increment')

四、模板变化

1. 多根节点(Fragment)

<!-- Vue 2:必须包裹单一根元素 --><template><div><header>...</header><main>...</main><footer>...</footer></div></template>
<!-- Vue 3:支持多根节点 --><template><header>...</header><main>...</main><footer>...</footer></template>

2. Teleport(瞬移组件)

<!-- Vue 3 新增,解决弹窗层级问题 --><template><button@click="show = true">打开弹窗</button><Teleportto="body"><divv-if="show"class="modal"><p>我渲染到了 body 下</p><button@click="show = false">关闭</button></div></Teleport></template>

3. 动态指令参数

<!-- Vue 3 支持动态指令参数 --><scriptsetup>constpropName=ref('title')consteventName=ref('click')</script><template><h1:[propName]="dynamicValue">动态绑定 v-bind 参数</h1><button@[eventName]="handler">动态绑定 v-on 参数</button></template>

4. 过滤器(Filters)被移除

<!-- Vue 2 --><p>{{ message | capitalize }}</p>
<!-- Vue 3:使用计算属性或方法替代 --><p>{{ capitalize(message) }}</p>
// Vue 3 <script setup>import{computed,ref}from'vue'constmessage=ref('hello')constcapitalize=(str)=>str.charAt(0).toUpperCase()+str.slice(1)

五、其他破坏性变更

特性Vue 2Vue 3
全局 APIVue.componentapp.component
原型挂载Vue.prototype.$httpapp.config.globalProperties.$http
事件总线Vue.prototype.$bus = new Vue()移除,推荐 mitt 或 Pinia
$children支持移除,用ref+defineExpose
$listeners支持合并到$attrs
自定义指令钩子bind,inserted,componentUpdatedbeforeMount,mounted,updated
keyCode 修饰符@keyup.13移除,用@keyup.enter
.sync 修饰符支持移除,用v-model:xxx

六、响应式原理差异

// Vue 2:Object.defineProperty// 问题:无法检测对象属性新增/删除,数组索引修改data(){return{user:{name:'张三'}}}// 下面这行不会触发视图更新this.user.age=25// 必须用 this.$set(this.user, 'age', 25)
// Vue 3:Proxyconstuser=reactive({name:'张三'})// 下面这行直接触发视图更新user.age=25

七、完整实战:TodoList 组件对照

Vue 2:

<template> <div> <input v-model="newTodo" @keyup.enter="addTodo" /> <ul> <li v-for="todo in todos" :key="todo.id"> <span :class="{ done: todo.completed }" @click="toggle(todo)"> {{ todo.text }} </span> <button @click="remove(todo)">删除</button> </li> </ul> </div> </template> <script> export default { data() { return { newTodo: '', todos: [ { id: 1, text: '学习 Vue 2', completed: true } ] } }, methods: { addTodo() { if (!this.newTodo.trim()) return this.todos.push({ id: Date.now(), text: this.newTodo, completed: false }) this.newTodo = '' }, toggle(todo) { todo.completed = !todo.completed }, remove(todo) { this.todos = this.todos.filter(t => t.id !== todo.id) } } } </script>

Vue 3(<script setup>):

<template> <div> <input v-model="newTodo" @keyup.enter="addTodo" /> <ul> <li v-for="todo in todos" :key="todo.id"> <span :class="{ done: todo.completed }" @click="toggle(todo)"> {{ todo.text }} </span> <button @click="remove(todo)">删除</button> </li> </ul> </div> </template> <script setup> import { ref } from 'vue' const newTodo = ref('') const todos = ref([ { id: 1, text: '学习 Vue 3', completed: true } ]) const addTodo = () => { if (!newTodo.value.trim()) return todos.value.push({ id: Date.now(), text: newTodo.value, completed: false }) newTodo.value = '' } const toggle = (todo) => { todo.completed = !todo.completed } const remove = (todo) => { todos.value = todos.value.filter(t => t.id !== todo.id) } </script>

八、迁移建议

💡Vue 2 已于 2023 年 12 月 31 日停止维护,新项目直接用 Vue 3 + Vite +<script setup>。

老项目迁移路径:

  1. 先升到 Vue 2.7:支持 Composition API 和<script setup>(兼容模式)
  2. 使用@vue/compat迁移构建:在 Vue 3 中兼容大部分 Vue 2 API,并给出弃用警告
  3. 逐组件迁移:新功能用 Composition API,旧组件逐步改写
  4. 替换全局 API:Vue.component→app.component等
  5. 移除废弃特性:过滤器、$children、.sync修饰符等
  6. 升级构建工具:从 Vue CLI(Webpack)迁移到 Vite

关键提醒:

  • <script setup>中ref必须加.value(模板中不需要)
  • reactive对象解构要用toRefs保持响应式
  • 生命周期钩子需要从vue导入
  • v-model默认 prop 从value改为modelValue

九、核心区别速记

维度Vue 2Vue 3
API 风格Options APIComposition API +<script setup>
响应式Object.definePropertyProxy
入口new Vue()createApp()
根节点单一根节点支持多根节点(Fragment)
v-model:value+@input:modelValue+@update:modelValue
过滤器支持移除,用 computed/method
TS 支持需装饰器,支持较弱原生 TypeScript 重写,类型友好
性能基准更小、更快、Tree-shaking 友好
新组件无Teleport、Suspense

Vue 3 不是简单的版本升级,而是开发范式的转变——从"按选项分类"到"按逻辑聚合"。刚开始写 Vue 3 可能会不习惯.value,但一旦适应 Composition API,你会发现复杂组件的代码组织能力有了质的飞跃。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询