一文读懂 Vue3 对比 Vue2 发生哪些变化
·
Vue3 相较于 Vue2 的核心升级是基于 Composition API 重构,同时重写了响应式系统、组件系统等核心模块,引入了大量新 API。
以下按「核心分类+API 详解+使用场景+对比 Vue2」的逻辑,全面梳理 Vue3 新增的关键 API:
一、核心背景:为什么引入新 API?
Vue2 基于「Options API」组织代码(data/methods/computed 等选项),存在两大痛点:
- 代码碎片化:关联逻辑(如「获取数据→渲染→监听变化」)分散在不同选项中,大型组件难以维护;
- 复用性差:mixins 存在命名冲突、来源不清晰的问题;
- TypeScript 支持弱:Options API 天生不贴合 TS 类型推导。
Vue3 的新 API 核心目标是:用 Composition API 聚合逻辑、提升复用性、完善 TS 支持,同时优化响应式、生命周期、组件通信等底层能力。
二、核心新 API 分类详解
(一)Composition API 核心(Vue3 最核心的新特性)
1. setup():组件的「逻辑入口」
- 作用:替代 Vue2 的
data/methods/computed/watch等选项,作为 Composition API 的执行上下文,所有组合式 API 都写在这里。 - 执行时机:组件创建前(
beforeCreate之后、created之前),此时this为undefined(无组件实例)。 - 参数:
props:组件接收的属性(响应式,不可解构,解构会丢失响应式);context:上下文对象,包含attrs/slots/emit/expose(替代 Vue2 的this.$attrs/this.$slots等)。
- 返回值:
- 对象:属性/方法可直接在模板中使用;
- 函数:自定义渲染函数(替代
render选项)。
- 示例:
<template> <div>{{ count }} {{ doubleCount }}</div> <button @click="increment">+1</button> </template> <script setup> import { ref, computed } from 'vue'; // 1. 响应式数据 const count = ref(0); // 2. 计算属性 const doubleCount = computed(() => count.value * 2); // 3. 方法 const increment = () => count.value++; </script> - 对比 Vue2:Vue2 需将
count放在data,increment放在methods,doubleCount放在computed,逻辑分散;Vue3 可按「功能逻辑」聚合。
2. ref():基本类型响应式
- 作用:为基本类型(字符串/数字/布尔) 创建响应式数据(Vue2 的
data无法单独为基本类型做响应式),也可包裹对象(内部会调用reactive)。 - 核心特点:
- 底层通过「包裹对象(
{ value: 原始值 })」实现响应式,访问/修改需通过.value(模板中自动解包,无需.value); - 支持所有基本类型,解决了 Vue2 响应式对基本类型的「遗漏」。
- 底层通过「包裹对象(
- 场景:单个数值、字符串等简单响应式数据(如计数器、输入框值)。
- 示例:
const name = ref('Vue3'); // 字符串响应式 const isShow = ref(true); // 布尔响应式 console.log(name.value); // 访问:Vue3 name.value = 'Vue3.4'; // 修改
3. reactive():对象/数组响应式
- 作用:为对象/数组创建「深层响应式」数据,底层基于 ES6
Proxy(而非 Vue2 的Object.defineProperty)。 - 核心优势(对比 Vue2):
- 支持数组响应式(Vue2 的
Object.defineProperty无法监听数组下标/长度变化); - 支持动态添加/删除对象属性(Vue2 需用
Vue.set/Vue.delete); - 深层响应式(默认递归所有子属性)。
- 支持数组响应式(Vue2 的
- 限制:
- 仅支持对象/数组,不支持基本类型;
- 解构会丢失响应式(需用
toRefs解决)。
- 示例:
const user = reactive({ name: '张三', age: 20, hobbies: ['篮球'] }); user.age = 21; // 响应式修改 user.hobbies.push('游泳'); // 数组响应式(Vue2 需用 $set) user.gender = '男'; // 动态添加属性(Vue2 需用 Vue.set)
4. computed():增强型计算属性
- 作用:创建响应式计算属性,Vue3 增强了「可写性」和「调试能力」。
- 核心升级:
- 支持「只读」和「可写」两种模式;
- 内置缓存,依赖变化才重新计算;
- 支持 TS 类型推导。
- 示例:
// 1. 只读模式(默认) const doubleCount = computed(() => count.value * 2); // 2. 可写模式 const fullName = computed({ get: () => `${firstName.value} ${lastName.value}`, set: (val) => { const [first, last] = val.split(' '); firstName.value = first; lastName.value = last; } }); fullName.value = '李四 王五'; // 触发setter - 对比 Vue2:Vue2 的 computed 仅支持函数形式(只读),可写需通过
set选项,但语法更繁琐;Vue3 语法更简洁,类型支持更好。
5. watch():增强型监听器
- 作用:监听响应式数据变化,Vue3 大幅增强了灵活性。
- 核心升级(对比 Vue2):
- 支持监听单个/多个源(ref/reactive/计算属性/函数返回值);
- 支持深度监听(无需手动设置
deep: true也可监听 reactive 深层属性); - 支持立即执行(
immediate: true)、防抖/节流; - 支持停止监听(返回停止函数)。
- 示例:
// 1. 监听单个ref watch(count, (newVal, oldVal) => { console.log(`count从${oldVal}变到${newVal}`); }, { immediate: true, deep: false }); // 2. 监听多个源 watch([count, name], ([newCount, newName], [oldCount, oldName]) => { console.log('count或name变化'); }); // 3. 监听reactive对象的单个属性(需用函数返回) watch(() => user.age, (newAge) => { console.log('年龄变化:', newAge); }); // 4. 停止监听 const stopWatch = watch(count, () => {}); stopWatch(); // 手动停止
6. watchEffect():自动收集依赖的监听器
- 作用:「副作用监听器」,立即执行一次,然后自动收集依赖,依赖变化时重新执行(无需指定监听源)。
- 核心特点:
- 「隐式监听」:无需手动声明监听谁,代码中用到的响应式数据都会被监听;
- 「立即执行」:默认
immediate: true,适合「初始化+监听」场景(如请求数据)。
- 场景:接口请求、DOM 操作等副作用场景(替代 Vue2 的
created+watch)。 - 示例:
// 初始化时请求数据,count变化时重新请求 const stop = watchEffect(() => { console.log('请求数据:count=', count.value); // fetch(`/api/data?count=${count.value}`).then(res => res.json()); }); // 停止监听 stop(); - 对比
watch:watch是「显式监听」(指定源),watchEffect是「隐式监听」(自动收集);watch可获取新旧值,watchEffect不能。
(二)响应式系统扩展 API(精细化控制响应式)
Vue3 提供了一系列「细粒度响应式 API」,解决 Vue2 响应式「一刀切」的问题:
| API | 作用 | 场景 |
|---|---|---|
readonly() |
创建「只读响应式」数据(禁止修改,修改会警告) | 保护全局状态、第三方数据,防止误修改 |
shallowRef() |
「浅响应式」ref(仅 .value 本身响应式,深层属性修改不触发更新) |
大对象/第三方库实例(如 Chart.js),仅需外层响应式 |
shallowReactive() |
「浅响应式」reactive(仅外层属性响应式,深层属性非响应式) | 大型表单对象,避免深层递归影响性能 |
toRef() |
将 reactive 对象的单个属性转为 ref(保留响应式) | 解构 reactive 对象的单个属性,或传递单个属性给子组件 |
toRefs() |
将 reactive 对象的所有属性转为 ref(批量) | 解构 reactive 对象,避免丢失响应式(如 const { name, age } = toRefs(user)) |
unref() |
语法糖:isRef(val) ? val.value : val(简化 .value 访问) |
通用函数中处理 ref/非 ref 数据 |
isRef()/isReactive()/isReadonly() |
判断数据类型(ref/reactive/只读) | 封装通用函数时校验参数类型 |
triggerRef() |
手动触发 shallowRef 的更新(修改深层属性后强制更新) | 修改 shallowRef 的深层属性,触发视图更新 |
示例(toRefs 解决解构响应式丢失):
const user = reactive({ name: '张三', age: 20 });
// 错误:解构后丢失响应式
const { name, age } = user;
// 正确:toRefs 转为 ref,保留响应式
const { name, age } = toRefs(user);
name.value = '李四'; // 响应式生效
(三)生命周期钩子(组合式)
Vue3 将 Vue2 的「选项式生命周期」改为「组合式钩子」,全部挂载在 setup 中执行,更贴合逻辑聚合:
| Vue2 选项式 | Vue3 组合式钩子 | 执行时机 | 备注 |
|---|---|---|---|
beforeCreate |
setup()(覆盖) |
组件实例创建前 | Vue3 无需单独声明 |
created |
setup()(覆盖) |
组件实例创建后 | Vue3 无需单独声明 |
beforeMount |
onBeforeMount |
组件挂载前 | |
mounted |
onMounted |
组件挂载后 | 最常用(DOM 操作、请求) |
beforeUpdate |
onBeforeUpdate |
组件更新前(数据变化后) | |
updated |
onUpdated |
组件更新后 | 避免在这修改数据(死循环) |
beforeDestroy |
onBeforeUnmount |
组件卸载前 | 清理定时器/事件监听 |
destroyed |
onUnmounted |
组件卸载后 | |
errorCaptured |
onErrorCaptured |
捕获子组件错误 | |
| - | onRenderTracked |
调试:响应式依赖被追踪时 | 仅开发环境生效 |
| - | onRenderTriggered |
调试:响应式依赖触发更新时 | 仅开发环境生效 |
示例:
import { onMounted, onBeforeUnmount } from 'vue';
onMounted(() => {
console.log('组件挂载完成,可操作DOM');
// 绑定事件
window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
console.log('组件卸载前,清理资源');
// 移除事件
window.removeEventListener('resize', handleResize);
});
(四)组件通信/高级 API
1. defineProps():声明组件属性
- 作用:替代 Vue2 的
props选项,在setup中声明组件接收的属性,支持 TS 类型校验。 - 核心优势:无需手动导入(
<script setup>中自动注入),类型推导更友好。 - 示例:
<script setup> // 方式1:运行时校验 const props = defineProps({ title: { type: String, required: true, default: '默认标题' }, count: { type: Number, default: 0 } }); // 方式2:TS 类型校验(推荐) const props = defineProps<{ title: string; count?: number; }>(); console.log(props.title); // 响应式,不可修改 </script>
2. defineEmits():声明组件事件
- 作用:替代 Vue2 的
emits选项,在setup中声明组件触发的事件,支持 TS 类型校验。 - 示例:
<script setup> // 方式1:运行时校验 const emit = defineEmits(['change', 'update']); // 方式2:TS 类型校验 const emit = defineEmits<{ (e: 'change', val: string): void; (e: 'update', val: number): void; }>(); // 触发事件 emit('change', '新值'); </script>
3. defineExpose():暴露组件属性/方法
- 作用:替代 Vue2 的
this.$refs访问子组件属性,Vue3 中<script setup>封装的组件默认关闭对外暴露,需手动defineExpose暴露。 - 示例:
<!-- 子组件 --> <script setup> const count = ref(0); const increment = () => count.value++; // 暴露给父组件 defineExpose({ count, increment }); </script> <!-- 父组件 --> <template> <Child ref="childRef" /> </template> <script setup> const childRef = ref(null); onMounted(() => { console.log(childRef.value.count); // 访问子组件属性 childRef.value.increment(); // 调用子组件方法 }); </script>
4. defineOptions()(Vue3.3+)
- 作用:在
<script setup>中设置组件选项(如name/inheritAttrs/components),无需额外写<script>标签。 - 示例:
<script setup> defineOptions({ name: 'MyComponent', // 组件名(用于递归/调试) inheritAttrs: false // 关闭属性继承 }); </script>
5. provide()/inject():跨层级通信升级
- 作用:Vue2 已有,但 Vue3 支持响应式传递,且可配合
ref/reactive实现跨层级数据同步。 - 示例:
// 父组件:提供数据 import { provide, ref } from 'vue'; const theme = ref('dark'); provide('theme', theme); // 传递响应式数据 // 子/孙组件:注入数据 import { inject } from 'vue'; const theme = inject('theme', ref('light')); // 第二个参数是默认值 theme.value = 'light'; // 响应式同步到父组件
(五)其他核心新 API
1. createApp():创建应用实例
- 作用:替代 Vue2 的
new Vue(),创建独立的应用实例,隔离不同应用的配置(如全局组件、指令)。 - 示例:
// Vue2 new Vue({ el: '#app', router, store }); // Vue3 import { createApp } from 'vue'; import App from './App.vue'; const app = createApp(App); app.use(router).use(store).mount('#app'); - 优势:多应用实例隔离(如一个页面多个 Vue 应用),避免全局配置污染。
2. Teleport:DOM 传送(组件)
- 作用:将组件的 DOM 结构传送到指定 DOM 节点(如
body),保留组件逻辑归属,解决弹窗/悬浮窗的样式嵌套问题(之前详细讲过)。 - 示例:
<Teleport to="body"> <div class="modal">弹窗内容</div> </Teleport>
3. Suspense:异步组件加载
- 作用:处理异步组件的加载状态(加载中/加载失败),替代手动写
v-if控制加载状态。 - 示例:
<template> <Suspense> <!-- 异步组件 --> <template #default> <AsyncComponent /> </template> <!-- 加载中 --> <template #fallback> <div>加载中...</div> </template> </Suspense> </template> <script setup> // 异步导入组件 const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue')); </script>
4. useAttrs()/useSlots():获取 attrs/slots
- 作用:替代 Vue2 的
this.$attrs/this.$slots,在<script setup>中获取组件的非 props 属性/插槽。 - 示例:
import { useAttrs, useSlots } from 'vue'; const attrs = useAttrs(); // 获取非props属性(如class、style) const slots = useSlots(); // 获取插槽(如默认插槽、具名插槽) console.log(attrs.class); // 访问class属性 console.log(slots.default()); // 渲染默认插槽
(六)废弃的 API(对比 Vue2)
Vue3 移除了部分冗余/有缺陷的 API,替代方案如下:
| Vue2 废弃 API | 替代方案 |
|---|---|
Vue.set/Vue.delete |
reactive 原生支持动态添加/删除属性 |
this.$on/$off/$once |
第三方事件库(如 mitt) |
filters |
computed 或自定义函数 |
$children |
$refs 或 provide/inject |
$scopedSlots |
合并到 slots,用 useSlots() 获取 |
new Vue() |
createApp() |
三、核心总结
Vue3 新 API 的设计核心是「灵活性、可组合性、类型友好」:
- Composition API(setup/ref/reactive/computed/watch)解决了 Vue2 逻辑碎片化、复用性差的问题;
- 细粒度响应式 API(readonly/shallowRef/toRefs 等)让响应式控制更精准;
- 组合式生命周期 贴合逻辑聚合,便于维护;
- 组件通信 API(defineProps/defineEmits/defineExpose)简化了组件交互,完善了 TS 支持;
- createApp/Teleport/Suspense 等扩展了 Vue 的核心能力,适配复杂场景。
对于开发者而言,优先掌握 setup/ref/reactive/computed/watch/onMounted 是基础,再根据场景学习细粒度响应式 API 和高级组件 API,即可覆盖 99% 的开发需求。
更多推荐


所有评论(0)