不定期更新,建议关注收藏点赞。
从 Vue 2 升级到 Vue 3,确实有很多需要更新的地方,特别是那些已废弃、不推荐再使用的特性,一不注意就会踩坑。别再使用它们啦!
用于迁移的构建版本(@vue/compat)
Vue 提供了 @vue/compat 构建版本,旨在帮助开发者逐步迁移项目。
默认以 Vue 2 模式运行,大多数 API 行为保持一致。
使用已更改或废弃的特性时,会在运行时发出警告。
可按组件或全局配置兼容性特性。
- 适用场景
将 Vue 2 应用升级到 Vue 3。
迁移库以支持 Vue 3。
帮助 Vue 2 开发者了解两个版本之间的差异。 - 注意
依赖于 Vue 2 内部 API 或未记录行为的项目可能无法顺利迁移。
Vue 3 不再支持 IE11。
服务器端渲染(SSR)配置需进行调整,例如使用 @vue/server-renderer 替代 vue-server-renderer。 - 升级流程
升级构建工具,如将 vue-loader 升级至 ^16.0.0。
在 package.json 中,将 vue 更新到 3.1,并安装相同版本的 @vue/compat。npm install @vue/compat
配置构建设置,为 vue 设置别名 @vue/compat,并通过 Vue 编译器选项开启兼容模式。
/*
如果你使用的是 Vue CLI,需要在项目根目录下的
vue.config.js 文件中配置别名:
*/
// vue.config.js
module.exports = {
chainWebpack: config => {
config.resolve.alias.set('vue', '@vue/compat')
}
}
/*
如果你使用的是 Vite,需要在 vite.config.js 中配置别名:
*/
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
alias: {
vue: '@vue/compat'
}
}
})
//启用兼容模式
//在 main.js 或 main.ts 文件中
import { createApp } from 'vue'
import App from './App.vue'
// 使用 Vue 3 和兼容模式
createApp(App).config.compatConfig = {
MODE: 2 // 开启兼容模式
}.mount('#app')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
根据需要,配置全局或组件级的兼容性特性。
组件注册与应用启动
功能 | Vue 2 | Vue 3 |
---|---|---|
创建实例 | new Vue({ … }) | createApp(App).mount(‘#app’) |
注册全局组件 | Vue.component(‘MyComp’, Comp) | app.component(‘MyComp’, Comp) |
注册插件 | Vue.use(plugin) | app.use(plugin) |
注册混入(慎用) | Vue.mixin(…) | app.mixin(…) |
注册指令 | Vue.directive(…) | app.directive(…) |
// Vue 3 启动方式:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
- 1
- 2
- 3
- 4
- 5
app.mixin() 用来注册全局混入逻辑,会影响所有组件。它的作用是把某些通用的选项、生命周期钩子或方法混入到每一个组件中。但不会混入props、emits、setup。混入优先级低于组件自身定义。全局污染大,慎用!推荐用:局部 mixin(只混入给某几个组件)或者 组合函数 composables(Vue 3 更推荐)
//组合函数
// useLog.js
import { onMounted } from 'vue'
export function useLog() {
onMounted(() => {
console.log('组件 mounted')
})
}
// MyComponent.vue
<script setup>
import { useLog } from './useLog'
useLog()
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
响应式系统升级(核心)
Vue 3 使用 ref 和 reactive 创建响应式变量,不再依赖老旧语法。
功能 | Vue 2 | Vue 3 |
---|---|---|
响应式对象 | data() { return { x: 1 } } | const state = reactive({ x: 1 }) |
响应式基本类型 | 手动通过 data 实现 | const count = ref(0) |
设置属性 | this.$set(obj, key, value) | 直接操作 reactive 对象 |
删除属性 | this.$delete(obj, key) | 直接 delete obj.key |
新增/删除属性响应式 | 不支持 | 支持 |
数据驱动视图(data-driven UI) | Object.defineProperty | ES6的Proxy |
- 数据驱动视图这里为啥升级
Vue 会在你读取数据时自动追踪依赖,你修改数据时自动触发更新视图。
Object.defineProperty 没有被废弃,它依然是 原生 JavaScript 的合法 API,而且在很多低层逻辑里依然有用。但vue 3 弃用了它作为响应式的核心方式,改用更强大的 Proxy。
场景 | Vue 2 | Vue 3 |
---|---|---|
实现响应式 | 使用 Object.defineProperty | 使用 Proxy |
设置响应式对象属性 | 必须 Vue.set(obj, ‘key’, val) | 直接操作即可 obj.key = val |
- Vue 2:用 Object.defineProperty 实现响应式
Vue 2 初始化时,会给每个属性都加上 getter 和 setter。
缺点:新增/删除属性不会响应式, 无法监听数组的索引变化,深层嵌套需要递归处理,性能差
//js
const obj = {}
Object.defineProperty(obj, 'msg', {
get() {
console.log('有人访问 msg')
return 'hello'
},
set(value) {
console.log('msg 被改成了', value)
}
})
obj.msg // 打印:有人访问 msg
obj.msg = 'hi' // 打印:msg 被改成了 hi
//vue
// Vue 内部用 defineProperty 给 count 加上 getter/setter
data() {
return {
count: 0
}
}
//原理
Object.defineProperty(data, 'count', {
get() {
// 收集依赖(谁用了我)
return value
},
set(newVal) {
// 通知视图更新
render()
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
Object.defineProperty它只能劫持对象里已经存在的属性,不能动态监听“新增的东西”。所以vue2必须要手动加上Vue.set(obj, 'age', 18) // Vue 2 特有的“补丁”
。所以 Vue 2 为了确保“所有属性都是响应式”,必须在初始化时就递归地给每一层加 getter/setter。
data() {
return {
user: {
profile: {
name: 'Tom'
}
}
}
}
//如果 Vue 2 只给 user 加 getter/setter,
this.user.profile.name = 'Jerry'
// ❌ 不触发视图更新!因为 Vue 根本没劫持 profile 和 name
//所以vue2必须递归每一层 挨个加上监听
function observe(obj) {
for (let key in obj) {
defineReactive(obj, key, obj[key])
if (typeof obj[key] === 'object') {
observe(obj[key]) // ⬅️ 关键:递归每一层
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- Vue 3:用 Proxy 全面升级响应式
优点:
所有属性都能监听(包括新增 / 删除)
无需递归,性能更优
数组也能精准追踪变化
更强大、现代,兼容性也没问题(IE 不支持 Proxy,但 Vue 3 本身也不支持 IE)
const state = reactive({ count: 0 })
//原理
const proxy = new Proxy(obj, {
get(target, key) {
track(target, key) // 收集依赖
return Reflect.get(target, key)
},
set(target, key, value) {
target[key] = value
trigger(target, key) // 触发更新
return true
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
补充:什么是ES6 Proxy
Proxy 是 ES6 新特性,它可以整个“代理”一个对象,对所有操作都能拦截处理(增删改查都行)。Vue 3 就是靠 Proxy 实现新的响应式系统
优点:监听新增/删除属性;无需递归嵌套,一次性代理整个对象,性能高;数组索引变更也能监听;
const obj = { count: 0 }
const proxy = new Proxy(obj, {
get(target, key) {
console.log(`访问 ${key}`)
return target[key]
},
set(target, key, value) {
console.log(`修改 ${key} 为 ${value}`)
target[key] = value
return true
}
})
proxy.count // 打印:访问 count
proxy.count = 10 // 打印:修改 count 为 10
//Vue 3 用 Proxy 是这样处理嵌套对象的
const obj = {
user: {
name: 'Tom'
}
}
const reactiveObj = reactive(obj)
// 当你访问 reactiveObj.user 时,才会给 user 创建 Proxy!
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
Proxy 不需要递归的关键:懒代理 (lazy observe)。它不在一开始递归所有层级,而是等你访问到那一层,再动态递归进去做代理,非常懒又聪明,性能爆表。
废弃与不推荐再使用的功能
功能 | 状态 | 替代 |
---|---|---|
this.$set / Vue.set() /this.$delete | ❌ 废弃 | 直接用 reactive 或 ref,delete |
keyCode 修饰符(@keyup.enter) | ⚠️ 不推荐 | 使用 @keyup.enter 字符 |
.sync 修饰符 | ⚠️ 不推荐 | 使用 v-model:propName 语法 |
$on, $off, $once | ❌ 废弃 | 推荐使用事件总线库,emits+用 props/emit 或 mitt |
filters(全局/局部) | ❌ 废弃 | 推荐使用方法或计算属性代替computed/methods |
inline-template | ❌ 废弃 | 拆分成子组件 |
propsData(用于 new Vue 时) | ❌ 废弃 | 用 createApp() 后传 props |
手工总线事件eventBus = new Vue() | ❌ 废弃 | 推荐用emits+ mitt或 props/emit |
- 不推荐再使用 keyCode 修饰符,理由不仅仅是“Vue 的风格改变”,而是更深层次的
keyCode 是浏览器官方废弃的规范,event.keyCode 属于老旧的 DOM 事件标准,已经被 官方废弃。新的标准推荐使用 event.key 代替。
event.key === 'Enter'//推荐
event.keyCode === 13 //废弃 不具备可读性
<!-- Vue 2 写法 -->
<input @keydown.enter="onEnter" />
<input @keydown.13="onEnter" /> <!-- ✅ 但难读 -->
<!-- Vue 3 推荐写法 -->
<input @keydown.enter="onEnter" />
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Vue 3 中仍支持 keyCode,但作为兼容功能存在。Vue 3 官方推荐使用 event.key 的形式:可以直接使用以下这些语义化的修饰符:
.enter
.tab
.delete
.esc / .escape
.space
.up / .down / .left / .right
- prop(全称是 property)就是 父组件传给子组件的数据。
.sync 修饰符是 Vue 2 中的一个经典“语法糖”,用于 子组件向父组件同步 prop 的值。在 Vue 3 中它也还支持,但使用场景和推荐方式略有不同。
//Vue 2 中没有 .sync 时
<!-- 父组件传值给子组件 -->
<Child :title="pageTitle" @update:title="val => pageTitle = val" />
//子组件内部这样触发:
this.$emit('update:title', '新标题')
//Vue 2 使用 .sync 写法更优雅
//.sync 其实是给你自动帮忙写了 @update:propName="..." 这一部分。
<Child :title.sync="pageTitle" />
//等于你写了:
:title="pageTitle"
@update:title="val => pageTitle = val"
//子组件内部触发同上
this.$emit('update:title', '新标题')
//Vue 3 推荐用 v-model 替代 .sync
<!-- 这是 Vue 3 中推荐的绑定子组件 prop 的方式 -->
<MyModal v-model:visible="isModalVisible" />
//子组件中:
// props: ['visible']
this.$emit('update:visible', false)//设置值为false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
this.$emit()
是 Vue 实例的方法,用来 触发(发出)一个自定义事件,来自于 Vue.prototype,只要你是一个组件(export default {} 里的东西),就可以用this.$emit('事件名', 参数1, 参数2...)
$on / $emit / $off / $once
是 Vue 2 中组件通信或事件总线(EventBus)常用的写法。$off()
取消监听,或$once()
只监听一次。
// event-bus.js
export const bus = new Vue()//这种手工事件总线 vue3已废弃
// A组件发消息
bus.$emit('custom-event', data)
// B组件监听消息
bus.$on('custom-event', (data) => { ... })
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
为啥废弃?
它不够清晰明确(“谁发的谁听的?”难追踪)
全局事件多了容易乱、难维护
不符合 函数式组件通信风格
Vue 3 不是基于 Vue.prototype 创建组件实例的,所以没这些方法了
Vue 3 推荐的替代方式:
- 使用 props + emit 这是组件通信的官方推荐方式!
组件更清晰;更容易自动提示 / 类型推导(对 TypeScript 支持也好);
<!-- 父组件 -->
<MyComponent @submit="handleSubmit" />
<!-- 子组件 -->
this.$emit('submit', data)
//Vue 3 新增emits:可以明确声明子组件会发什么事件
export default {
emits: ['submit']
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
补充:emits 是 Vue 3 中专门用来 显式声明组件能触发哪些事件 的配置项。
就像你在 props 里声明你能“接收”哪些数据,emits 就是声明你能“发出”哪些事件。
优点:显式声明你这个组件会发出哪些事件、更利于维护和自动化(比如 eslint 检查、IDE 智能提示)、对typescript更友好
- 多组件/跨层通信,用第三方库:mitt
优点:极简(仅 200 行)、更好组织、更强控制力
全局通信慎用
npm i mitt //install
// event-bus.js
import mitt from 'mitt'
export const bus = mitt()
// A组件
bus.emit('save', data)
// B组件
bus.on('save', (data) => { ... })
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
性能提升和 TypeScript 支持更好
更好的 Tree Shaking。
原生支持 TypeScript(setup + < script setup> 非常方便)。
更快的渲染和更小的包体积。
生命周期钩子
Composition API 中生命周期钩子改名了
生命周期 | Vue 2 | Vue 3(Composition API) |
---|---|---|
beforeCreate | beforeCreate() | setup() 前运行,无等价钩子 |
created | created() | setup() 里处理初始化逻辑 |
beforeMount | beforeMount() | onBeforeMount() |
mounted | mounted() | onMounted() |
beforeUpdate | beforeUpdate() | onBeforeUpdate() |
updated | updated() | onUpdated() |
beforeDestroy | beforeDestroy() | onBeforeUnmount() |
destroyed | destroyed() | onUnmounted() |
//vue2
created() {
console.log('组件创建')
}
//vue3
import { onMounted } from 'vue'
onMounted(() => {
console.log('组件挂载')
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Composition API 替代 Options API(推荐,但非强制)
组合式替代了选项式风格
Vue 2 中,没有 .value,因为 Vue 2 使用的响应式系统与 Vue 3 不同,它直接使用 getter 和 setter 来实现数据绑定。
- ref vs reactive:
ref:用于创建 基本类型(如 string、number、boolean 等)的响应式引用。通过 .value 来访问和修改其值。如果在模板中直接使用名称没有.value,Vue 会自动解包 ref 并正确显示 x.value 的值。
reactive:用于创建 对象和数组 的响应式引用。直接访问属性(无需 .value)。 - 汇总
功能 | Vue 2 | Vue 3 |
---|---|---|
响应式数据 | data() | ref()、reactive() |
方法 | methods: {} | setup() { const fn = () => {} } |
计算属性 | computed: {} | computed(() => x.value + 1) |
监听器 | watch: {} | watch() / watchEffect() |
生命周期钩子 | mounted() 等 | onMounted() 等详见上面章节 |
template 中使用 | this.count | count.value(注意 .value) |
//vue2 类似于类的写法
export default {
data() { return { count: 0 } },
methods: {
increment() { this.count++ }
}
}
//vue3
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 计算属性(computed)是根据某些数据 自动计算出值 的方法,通常用来处理 复杂逻辑,并且 缓存结果,避免每次访问都重新计算。
- Vue 3 中的 watch 和 watchEffect 主要是用来处理响应式数据变化时执行副作用的工具。它们在 Composition API 中尤其重要,因为它们是 Vue 3 中响应式系统的核心功能之一。
watch 的作用:watch 是一个 侦听器,用来观察一个或多个响应式数据的变化,触发一个副作用(通常是执行某些操作,如请求数据或修改其他数据)。
在 Vue 3 中,watch 是作为一个函数使用的,可以监听一个或多个响应式数据的变化。
在Vue 2 中。 watch 是“选项式 API”里一个配置项在 Vue 2 中,写在组件的配置对象里(比如 data, methods, computed 等并列),是一个 对象(Object),也就是我们说的“选项式 API”。
- watch vs watchEffect
watchEffect 是 Vue 3 中的另一个工具,和 watch 的区别在于它 自动收集依赖,只要你在函数体内使用响应式数据,它就会自动跟踪这些数据,并在这些数据发生变化时重新执行副作用。
watchEffect 不需要显式指定监听的数据,它会自动收集依赖,只要你在回调函数内访问了响应式数据,它就会自动追踪。
watchEffect 是 立即执行 的,监听的数据发生变化时会重新执行。
适用于副作用代码,例如请求数据、计算值等。
特性 | watch | watchEffect |
---|---|---|
依赖收集 | 手动指定要监听的数据 | 自动收集依赖(自动追踪数据) |
执行时机 | 不立即执行,只有数据变化时才执行 | 初次执行时立即执行,之后每次依赖变化时执行 |
用途 | 适合处理 明确的数据监听,比如某个数据变化时执行特定操作 | 适合处理 副作用操作,如数据变化触发的异步请求等 |
返回值 | 返回停止侦听的函数(stop()) | 返回一个停止侦听的函数(stop()) |
不能用watcheffect完全替换watch,这是因为:
特性 | watchEffect() | watch() |
---|---|---|
是否自动追踪依赖 | ✅ 自动 | ❌ 手动指定 |
是否能拿到新旧值 | ❌ 无法(没有 oldVal) | ✅ 能获取 (newVal, oldVal) |
是否能监听多个数据 | ✅ 可,但要写在 effect 内部 | ✅ 明确传数组 |
是否可监听 getter 返回值 | ✅ 自动 | ✅ 明确写 getter |
用途倾向 | ✅ 处理副作用(如自动请求) | ✅ 处理逻辑清晰的监听需求 |
是否支持懒执行 | ❌ 总是立即执行 | ✅ 默认懒执行,可设 immediate: true |
可以用 watchEffect 替代的情况:
适用于:
不关心具体新旧值,只要数据变了就触发逻辑
逻辑简单,函数体里自然访问了响应式变量
副作用自动触发即可,比如发请求、打印日志
在开发早期、快速调试时用 watchEffect。
后期逻辑明确或复杂时,切换为 watch。
//vue2
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
watch: {
count(newVal, oldVal) {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
}
}
}
</script>
//vue3
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
setup() {
const count = ref(0);
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
return {
count
};
}
}
</script>
//watchEffect
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const count = ref(0);
watchEffect(() => {
console.log(`count 的值是: ${count.value}`);
});
return {
count
};
}
}
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
模版语法&绑定
功能 | Vue 2 | Vue 3 |
---|---|---|
v-model 默认绑定 | v-model=“value” | v-model=“modelValue”(等价) |
多个 v-model | 不支持 | ✅ v-model:title=“title”,多个 prop 可同时绑定 |
事件修饰符 .keyCode | @keyup.13 | ❌ 废弃,改用 @keyup.enter 等 |
.sync 修饰符 |
| ✅ 用 v-model:title="msg" 替代 |
filters | {{ msg | capitalize }} | 用计算属性(computed)或用方法(function) |
{{ msg | capitalize }}
是 Vue 2 中的“过滤器(filters)语法”,但 在 Vue 3 中已被移除(废弃)。在 Vue 2 中,filters 是模板语法中的一个“管道式”语法,用于对数据进行格式化处理,类似于小型的数据处理函数。
<template>
<p>{{ msg | capitalize }}</p>
</template>
<script>
export default {
data() {
return {
msg: 'hello world'
}
},
filters: {
capitalize(value) {
if (!value) return ''
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
}
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
Vue 3 中为什么废弃 filters?官方理由:
不符合函数式编程思维:filters 看似语法糖,实则引入了“非显式依赖”。
容易误用:开发者容易把复杂逻辑放到 filters 中,使模板变得难以维护。
难以移植与复用:filters 是模板专用,不适用于 JS 或 setup 中逻辑。
Composition API 中移除 filters。
- Vue 3 Composition API 的核心思想是:
所有逻辑都可以复用、组合、可测试、可类型推导
强调:逻辑 = 函数化,不要“藏”在模板里。
而 filters:
特点 | 为什么不被推荐 |
---|---|
模板专属语法 | 无法在 JS 代码中使用 |
不利于逻辑复用 | 只能在当前组件中用 |
不透明 | IDE 不好识别、提示 |
和 setup() 不兼容 | setup 函数中没地方注册 filters |
不利于类型检查 | 缺少显式声明方式 |
存在更优替代方案 | 用 computed / 函数更清晰 |
组件通信
功能 | Vue 2 | Vue 3 |
---|---|---|
Props 传参 | props: [‘title’] | props: [‘title’](保持不变) |
子组件触发事件 | this.$emit(‘xxx’)没废弃,但适用选项式API | defineEmits([‘xxx’])推荐组合式API使用 |
父组件监听事件 | ||
$on, $off | ✅ 可用 | ❌ 已废弃,推荐使用 mitt 等第三方库 |
新语法推荐
- v-model 语法升级
- Fragment 支持(一个组件可以返回多个根节点)
<!-- Vue 3 终于不用写额外的 <div> 包裹了 -->
<template>
<h1>Hello</h1>
<p>World</p>
</template>
- 1
- 2
- 3
- 4
- 5
功能 | Vue 2 | Vue 3 推荐写法 |
---|---|---|
单文件组件 |
| ✅ 语法糖,写法更清爽 |
多根节点(Fragment) | ❌ 不支持 | ✅ 默认支持 |
v-model 的值名 | prop是value / 事件是 input | modelValue / update:modelValue |
传递多个 v-model | ❌ 不支持v-model="value" | ✅ v-model:title / v-model:content/v-model:value=“value”(可多个) |
slot 写法 | | ✅ 一样,支持 |
具名插槽传参 | slot-scope=“props” | v-slot=“props”(在 Vue 2.6 开始统一,Vue 3 延续) |
- emits 明确声明子组件触发的事件
- watch / watchEffect 监听变化
- teleport 将组件渲染到 DOM 外层
Vue 3 中的 < teleport> 是一个非常实用的内置组件,它的作用就是:将某个子组件或元素“传送”到 DOM 树中的另一个位置(也就是 脱离父组件的 DOM 层级,渲染到别处去)。
在一些场景下,你可能希望组件 逻辑上属于某个父组件,但 实际渲染的位置不在它的 DOM 结构里,比如:
Modal 弹窗(通常要挂在 下)
Tooltip 气泡提示
Dropdown 下拉菜单
Toast 通知提示
这些都希望脱离当前组件的 DOM 层级,防止被 overflow: hidden 或 z-index 限制。
<template>
<teleport to="body">//内容仍然是组件的一部分,响应式和事件绑定都正常工作。
//to="body":指定目标 DOM 节点,把这个元素 “传送”到 document.body 下。
//目标元素需要在页面中真实存在(Vue 不会自动创建目标 DOM)。
<div class="modal">
我是一个脱离当前 DOM 层级的弹窗
</div>
</teleport>
</template>
//example
<template>
<div>
<button @click="show = true">打开弹窗</button>
<teleport to="body">
<div v-if="show" class="modal">
<p>我是弹窗内容</p>
<button @click="show = false">关闭</button>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
<style scoped>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border: 1px solid #aaa;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- suspense 异步组件加载更优雅
Suspense 不适用于 Vue 2,Vue 2 的异步组件加载只能用 v-if + loading 方式处理。
< Suspense>,它让异步组件的加载变得更优雅、更顺滑,尤其是在等待数据、懒加载等场景下提升用户体验。
< Suspense> 是 Vue 3 中的一个内置组件,用于等待异步组件加载完成前,显示一个“占位内容(fallback)”,加载完毕后自动切换到真正的组件内容。支持嵌套多个异步子组件。
适用于:
懒加载组件
异步 setup 函数(例如等待 API 请求完成)
SSR/异步数据加载
复杂组件初始化中
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
//fallback 只能显示“静态内容”(不能是 reactive 响应式逻辑)。
<div>加载中,请稍候...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./MyHeavyComponent.vue')
)
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
AsyncComponent 是异步加载的组件(用 defineAsyncComponent() 定义)。
< Suspense> 会:
先显示 fallback 插槽里的内容(如“加载中…”)
当组件加载完毕后,自动渲染 default 插槽的内容(也就是你真正的组件)
评论记录:
回复评论: