Vue3与Vue2中使用对比
一、共有的API
1、修饰符:
v-on 修饰符:.stop、.prevent、.self、.once、.passive 等;
v-model 修饰符:.lazy、.number、.trim 等;
用法与行为在 Vue 2 和 Vue 3 中完全一致。
.stop:阻止事件冒泡
作用:调用 event.stopPropagation(),阻止事件从子元素冒泡到父元素。
场景:当父子元素都绑定了事件,点击子元素时不想触发父元素的事件。
<template><div @click="handleParentClick" style="padding: 20px; background: #eee;">父元素<button @click.stop="handleChildClick">子按钮</button></div>
</template><script setup>
const handleParentClick = () => console.log('父元素事件触发');
const handleChildClick = () => console.log('子元素事件触发');
</script>
结果:点击按钮时,只打印「子元素事件触发」(若不加 .stop,会同时打印父子事件)。
.self:仅自身触发事件
作用:事件仅在事件目标是当前元素自身时触发,忽略子元素冒泡上来的事件。
场景:父元素有事件,但只想在点击父元素自身(非子元素)时触发。
<template><div @click.self="handleParentClick" style="padding: 20px; background: #eee;">父元素(点击这里触发)<button>子按钮(点击我不触发父事件)</button></div>
</template><script setup>
const handleParentClick = () => console.log('父元素自身被点击');
</script>
结果:点击父元素的空白区域(非按钮)会触发事件,点击子按钮不会触发(即使按钮在父元素内部)。
.once:事件只触发一次
作用:事件触发一次后自动解绑,后续点击不再生效。
场景:限制事件只能执行一次(如「同意协议」按钮)。
<template><button @click.once="handleClick">点击我(只生效一次)</button>
</template><script setup>
const handleClick = () => console.log('按钮被点击了');
</script>
结果:第一次点击打印日志,第二次及之后点击无反应。
.passive:优化滚动 / 触摸事件性能
作用:告诉浏览器「此事件不会调用 preventDefault()」,浏览器可提前优化渲染(避免滚动卡顿)。
场景:监听 scroll、touchmove 等高频触发的事件(尤其在移动端)。
<template><!-- 滚动列表时优化性能 --><div @scroll.passive="handleScroll" style="height: 200px; overflow: auto; border: 1px solid #ccc;"><p v-for="i in 100" :key="i">列表项 {{ i }}</p></div>
</template><script setup>
const handleScroll = () => console.log('滚动中...');
</script>
注意:
- 不能和 .prevent 同时使用(会报错,因为 .passive 已声明不阻止默认行为)。
- 主要用于提升移动端滚动流畅度。
2、动态组件
动态组件用于通过 component 标签和 is 属性动态渲染不同组件,核心用法在 Vue2 和 Vue3 中保持一致。
差异点:Vue3 中 is 属性支持直接绑定组件对象(无需注册),而 Vue2 中若绑定对象需先在 components 中注册。
Vue2
<!-- Vue2 和 Vue3 通用 -->
<component :is="currentComponent"></component><script>
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'export default {components: { ComponentA, ComponentB },data() {return {currentComponent: 'ComponentA' // 可动态切换为 ComponentB}}
}
</script>
Vue3
在 Vue 3 中,基本用法与 Vue 2 类似,也是通过 <component :is="...">来实现。
<template><component :is="currentComponent"></component>
</template><script setup>
import { ref } from 'vue'
import CompA from './CompA.vue'
import CompB from './CompB.vue'const currentComponent = ref('CompA') // 如果注册了全局或局部组件
// 或者
const currentComponent = ref(CompA) // 直接使用组件对象
</script>
注意:
- 在 Vue 3 的 Composition API + <script setup>中,如果你使用的是组件对象(如 CompA),直接传入即可。
- 如果你使用的是字符串形式的组件名(如 'CompA'),则需要确保该组件已经通过 components选项注册(在 Options API 中)或在 <script setup>中通过 defineAsyncComponent或其它方式导入并使用。如果使用 Options API 的写法(Vue 3 同样支持):
<script>
import CompA from './CompA.vue'
import CompB from './CompB.vue'export default {components: {CompA,CompB},data() {return {currentComponent: 'CompA'}}
}
</script>
3、异步组件
异步组件用于按需加载组件(代码分割),Vue3 对其 API 进行了重构,与 Vue2 差异较大。
Vue2
通过直接返回一个 Promise 定义,或使用 () => import() 语法:
// Vue2 异步组件定义
const AsyncComponent = () => import('./AsyncComponent.vue')// 带选项的异步组件(如加载状态、错误状态)
const AsyncComponentWithOptions = () => ({component: import('./AsyncComponent.vue'),loading: LoadingComponent, // 加载中显示的组件error: ErrorComponent, // 加载失败显示的组件delay: 200, // 延迟显示加载组件(毫秒)timeout: 3000 // 超时时间(毫秒)
})
Vue 3
Vue3 引入了 defineAsyncComponent 方法统一处理异步组件,支持与 <Suspense> 配合,且选项结构有调整:
// Vue3 基础用法(需导入 defineAsyncComponent)
import { defineAsyncComponent } from 'vue'const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue')
)// 带选项的异步组件
const AsyncComponentWithOptions = defineAsyncComponent({loader: () => import('./AsyncComponent.vue'), // 替代 Vue2 的 componentloadingComponent: LoadingComponent,errorComponent: ErrorComponent,delay: 200,timeout: 3000,suspensible: false // 新增:是否允许被 Suspense 控制(默认 true)
})
使用方式与 Vue 2 类似,在模板中:
<component :is="AsyncComp"></component>
关键区别:
- Vue 2 是直接返回一个带有 component, loading, error等字段的对象。
- Vue 3 则是使用 defineAsyncComponent工厂函数来创建异步组件,更加规范和清晰。
- Vue 3 中的异步组件定义方式更统一,也更易于维护。
另外,在 Vue 3 中,简单的异步组件可以更简洁地写为:
const AsyncComp = defineAsyncComponent(() => import('./MyComponent.vue'))
也就是没有加载状态、错误状态等配置的简化形式。
4、插槽
Vue 3 和 Vue 2 在 插槽(Slots) 的使用上有一些重要的区别,主要体现在 作用域插槽(Scoped Slots) 的语法、API 设计以及插槽的底层实现上。下面从几个方面详细对比它们的区别:
4.1 基础插槽(默认插槽)
Vue 2
<!-- 子组件 Child.vue -->
<template><div><slot></slot> <!-- 默认插槽 --></div>
</template><!-- 父组件使用 -->
<Child><p>这是插入到子组件默认插槽的内容</p>
</Child>
Vue 3
<!-- 子组件 Child.vue -->
<template><div><slot></slot> <!-- 默认插槽,写法相同 --></div>
</template><!-- 父组件使用 -->
<Child><p>这是插入到子组件默认插槽的内容</p>
</Child>
✅ 结论: 基础插槽在 Vue 2 和 Vue 3 中 用法完全一致。
4.2 具名插槽(Named Slots)
Vue2
<!-- 子组件 Child.vue -->
<template><div><header><slot name="header"></slot></header><main><slot></slot> <!-- 默认插槽 --></main></div>
</template><!-- 父组件使用 -->
<Child><template slot="header"><h1>这是头部</h1></template><p>这是默认插槽内容</p>
</Child>
Vue 3
<!-- 子组件 Child.vue -->
<template><div><header><slot name="header"></slot></header><main><slot></slot> <!-- 默认插槽 --></main></div>
</template><!-- 父组件使用:推荐新语法 -->
<Child><template #header><h1>这是头部</h1></template><p>这是默认插槽内容</p>
</Child>
🔁 语法糖变化:
- Vue 2 使用 slot="header"属性方式。
- Vue 3 推荐使用 #header(即 v-slot:header 的简写),但 slot="header"仍然兼容(不推荐)。
✅ 结论:
具名插槽功能一样,但 Vue 3 引入了更简洁的 #语法糖(v-slot 的简写),推荐使用。
4.3 作用域插槽(Scoped Slots) / 插槽传参
这是 Vue 2 和 Vue 3 差异较大的地方。
Vue 2 中的作用域插槽
作用域插槽允许子组件向父组件传递数据,父组件通过 slot-scope来接收。
<!-- 子组件 Child.vue -->
<template><div><slot :user="user"></slot> <!-- 向插槽传递数据 --></div>
</template><script>
export default {data() {return {user: { name: 'Alice' }};}
};
</script><!-- 父组件使用 Vue 2 写法 -->
<Child><template slot-scope="props"><p>来自子组件的用户:{{ props.user.name }}</p></template>
</Child>
或者对于 具名作用域插槽:
<!-- 子组件 -->
<slot name="user" :user="user"></slot><!-- 父组件 -->
<Child><template slot="user" slot-scope="props"><p>{{ props.user.name }}</p></template>
</Child>
🔹 关键点:
- 使用 slot-scope接收子组件传递的数据。
- 对于具名插槽,需要同时使用 slot="name"和 slot-scope。
Vue 3 中的作用域插槽
Vue 3 废弃了 slot-scope和 slot属性,统一使用 v-slot(或 #缩写) 来接收所有类型的插槽,包括作用域插槽。
<!-- 子组件 Child.vue -->
<template><div><slot :user="user"></slot> <!-- 作用域插槽,传递数据 --></div>
</template><script setup>
import { ref } from 'vue'
const user = ref({ name: 'Alice' })
</script><!-- 父组件使用 Vue 3 推荐写法 -->
<Child><template #default="slotProps"><p>来自子组件的用户:{{ slotProps.user.name }}</p></template>
</Child>
或者更简洁地,因为是默认插槽,也可以直接:
<Child><template #default="{ user }"><p>来自子组件的用户:{{ user.name }}</p></template>
</Child>
对于具名作用域插槽:
子组件:
<slot name="user" :user="user"></slot>
父组件:
<Child><template #user="{ user }"><p>用户名:{{ user.name }}</p></template>
</Child>
🔹 关键点:
- Vue 3 不再使用 slot和 slot-scope属性。
- 所有插槽(包括作用域插槽)都通过 v-slot指令(或 #缩写) 来处理。
- v-slot可以解构传递的参数,如 #default="{ user }"。
5、KeepAlive
在 Vue 2 和 Vue 3 中,KeepAlive 都是用于缓存组件实例以避免重复渲染的内置组件,但两者在使用方式、API 设计和功能细节上存在一些异同,具体如下:
5.1、相同点
核心功能一致
无论是 Vue 2 还是 Vue 3,KeepAlive 的核心作用都是缓存包裹的组件,使其在切换时不被销毁,保留组件的状态(如表单输入、滚动位置等),从而提升性能。
基本用法相似
都通过包裹动态组件(component 标签)或路由组件来实现缓存,例如:
<!-- Vue 2 和 Vue 3 通用写法 -->
<keep-alive><component :is="currentComponent"></component>
</keep-alive>
5.2、不同点
维度 | Vue 2 | Vue 3 |
---|---|---|
组件名大小写 | 要求组件名必须为 ** PascalCase **(如 MyComponent ),否则 include/exclude 可能匹配失败。 | 支持 ** kebab-case **(如 my-component )和 PascalCase,匹配更灵活(推荐与组件注册名一致)。 |
max 属性 | 不支持 max 属性,缓存数量无限制。 | 新增 max 属性,用于限制最大缓存组件数量(超出时会销毁最久未使用的组件,类似 LRU 缓存)。 |
生命周期钩子 | 被缓存组件激活 / 失活时,触发 activated 和 deactivated 钩子(仅在 KeepAlive 包裹时生效)。 | 同样支持 activated 和 deactivated 钩子,** 但在组合式 API 中需配合 onActivated 和 onDeactivated 使用 **。 |
与路由结合 | 需配合 <router-view> 直接包裹:<keep-alive><router-view></router-view></keep-alive> | 用法相同,但 Vue 3 的路由组件缓存逻辑更清晰,且支持通过路由元信息(meta )控制缓存(需结合 include )。 |
v-slot 用法 | 不支持,无法直接访问缓存组件的实例。 | 支持 v-slot ,可通过 default 插槽获取缓存组件的实例和状态:vue<br><keep-alive v-slot="{ component }"><br> <component :is="component" /><br></keep-alive><br> |
组件注册方式 | 全局内置组件,无需额外导入即可使用。 | 同样是全局内置组件,但在 <script setup> 或组合式 API 中无需导入,直接使用。 |
缓存键(key) | 默认基于组件 ID 和标签名生成缓存键。 | 缓存键生成逻辑优化,更稳定,且支持通过组件的 key 属性手动指定缓存标识(避免同类型组件缓存冲突)。 |
5.3 典型场景差异示例
限制缓存数量(Vue 3 新增)
<!-- Vue 3 中限制最多缓存 3 个组件 -->
<keep-alive :max="3"><component :is="currentComponent"></component>
</keep-alive>
组合式 API 中使用生命周期(Vue 3)
<script setup>
import { onActivated, onDeactivated } from 'vue'onActivated(() => {// 组件被激活时触发console.log('组件激活')
})onDeactivated(() => {// 组件被缓存时触发console.log('组件缓存')
})
</script>
v-slot 访问缓存组件(Vue 3)
<keep-alive v-slot="{ component }"><component :is="component" @custom-event="handleEvent" />
</keep-alive>
二、Vue2独有
1、$set(target, key, value):
在响应式对象 / 数组中添加属性并触发更新(Vue 2 中解决 Object.defineProperty 监听限制;Vue 3 中因 Proxy 可直接赋值,但仍保留该方法兼容)。
2、$delete(target, key):
删除响应式对象 / 数组的属性并触发更新(同理,Vue 3 中可直接删除,但保留方法)。
3、混入 (mixin)
在 Vue 2 中,混入(Mixin) 是一种分发 Vue 组件中可复用功能的灵活方式。一个 mixin 对象可以包含任意组件选项(如 data、methods、computed、生命周期钩子等)。当组件使用 mixin 时,所有 mixin 对象的选项将被“混合”进入该组件本身的选项中。
3.1、Mixin 的作用
Mixin 的主要作用是:
- 代码复用:将多个组件共用的逻辑(如方法、数据、计算属性、生命周期等)抽离出来,避免重复代码。
- 模块化:将功能模块化,提高代码的可维护性和组织性。
- 组合逻辑:通过混入不同的 mixin,组合出具有不同能力的组件。
3.2、Mixin 的基本使用
定义一个 Mixin
Mixin 是一个普通的 JavaScript 对象,它包含 Vue 组件选项中的任何内容,例如:
// myMixin.js
export const myMixin = {data() {return {mixinMessage: '这是来自 mixin 的数据'}},methods: {mixinMethod() {console.log('这是来自 mixin 的方法');}},created() {console.log('mixin 的 created 钩子被调用');}
}
在组件中使用 Mixin
在 Vue 组件中,通过 mixins选项引入 mixin:
<template><div><p>{{ mixinMessage }}</p><button @click="mixinMethod">调用 mixin 方法</button></div>
</template><script>
import { myMixin } from './myMixin.js'export default {mixins: [myMixin], // 使用 mixindata() {return {componentMessage: '这是组件自身的数据'}},created() {console.log('组件的 created 钩子被调用');}
}
</script>
3.3、Mixin 的合并策略
当组件和 mixin 具有相同的选项时,Vue 会按一定的规则进行合并:
选项类型 | 合并策略 |
---|---|
| 组件的 |
| 后者(组件自身)会覆盖前者(mixin)的同名属性 |
| 同名的钩子函数会合并为一个数组,mixin 的钩子先执行,组件的后执行 |
| 同名时,组件的定义会覆盖 mixin 的定义 |
示例:生命周期钩子的执行顺序
// mixin
const mixin = {created() {console.log('mixin created')}
}// 组件
export default {mixins: [mixin],created() {console.log('component created')}
}
输出顺序为:
mixin created
component created