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 的 component
  loadingComponent: 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 缓存)。
生命周期钩子 被缓存组件激活 / 失活时,触发 activateddeactivated 钩子(仅在 KeepAlive 包裹时生效)。 同样支持 activateddeactivated 钩子,** 但在组合式 API 中需配合 onActivatedonDeactivated 使用 **。
与路由结合 需配合 <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], // 使用 mixin
  data() {
    return {
      componentMessage: '这是组件自身的数据'
    }
  },
  created() {
    console.log('组件的 created 钩子被调用');
  }
}
</script>

3.3、Mixin 的合并策略

当组件和 mixin 具有相同的选项时,Vue 会按一定的规则进行合并:

选项类型 合并策略
data 组件的 data会覆盖 mixin 的 data(深度合并对象)
methods, components, directives 后者(组件自身)会覆盖前者(mixin)的同名属性
生命周期钩子 同名的钩子函数会​​合并为一个数组​ ​,​​mixin 的钩子先执行,组件的后执行​
computed, props, watch 同名时,组件的定义会覆盖 mixin 的定义

示例:生命周期钩子的执行顺序​

复制代码
// mixin
const mixin = {
  created() {
    console.log('mixin created')
  }
}

// 组件
export default {
  mixins: [mixin],
  created() {
    console.log('component created')
  }
}

输出顺序为:​

复制代码
mixin created
component created
相关推荐
Jeffrey__Lin2 天前
解决ElementPlus使用ElMessageBox.confirm,出现层级低于el-table的问题
前端·javascript·elementui·vue·elementplus
麦麦大数据2 天前
F024 RNN+Vue+Flask电影推荐可视化系统 python flask mysql 深度学习 echarts
python·rnn·深度学习·vue·echarts·电影推荐
HECHEN****3 天前
Composition API 与 React Hook 很像,区别是什么?
vue·面试题
知识分享小能手3 天前
微信小程序入门学习教程,从入门到精通,项目实战:美妆商城小程序 —— 知识点详解与案例代码 (18)
前端·学习·react.js·微信小程序·小程序·vue·前端技术
cgsthtm3 天前
RuoYi.Net后端返回雪花ID前端精度丢失问题
oracle·vue·精度丢失·雪花id·ruoyi.net
玩代码3 天前
使用 nvm(Node Version Manager) 高效管理Node.js
node.js·vue·nvm
bdawn4 天前
Vue3 项目首屏加载性能优化全攻略
性能优化·vue·策略·分包
Orange_sparkle4 天前
若依使用基本步骤
java·vue
肖祥4 天前
uni-app x封装request,统一API接口请求
vue