1、Vue3和Vue2的区别
使用了Composition API,提高了代码逻辑的可复用性。
引入了Fragment,允许组件有多个根节点。
更好的TypeScript支持。
使用了更小的包体积和更高效的运行时性能。
提供了Suspense组件,用于处理异步组件的加载状态。
Teleport组件允许将子组件渲染到DOM中的任何位置
2、vue2和vue3在虚拟DOM的处理上有什么不同
vue2的虚拟DOM瓶颈:
- 全量 Diff:每次更新需对比新旧虚拟 DOM 树的所有节点,即使某些节点是静态的。
- 静态节点重复创建:模板中的静态内容在每次渲染时都会重新生成虚拟节点。
- 事件监听冗余:事件处理函数在每次更新时可能被重新绑定
- 内存占用高:虚拟节点对象包含完整属性,占用内存较大。
vue3虚拟DOM的核心优化
1、编译时优化:静态提升、patch Flags(补丁标记)、Block Tree(区块树)、缓存事件处理函数
2、Diff算法优化:同层同序对比(与Vue2一致)、最长递增子序列(LIS)优化
3、组合式API协同优化:更细的响应式追踪(proxy)、复用的代码逻辑(Hook、Setup)
生产环境优化
Tree Sharking支持:vue3允许打包工具移除未使用的代码
总结
Vue 3 的虚拟 DOM 重构通过以下设计实现性能飞跃:
- 编译时静态分析:通过静态提升、Patch Flags、Block Tree 减少运行时计算量。
- 智能 Diff 算法:结合 LIS 和动态标记,最小化 DOM 操作。
- 响应式系统协同:精准追踪依赖,避免无效渲染。
3、Vue3中的Teleport有什么作用
Teleport是Vue 3中引入的一个新特性,它允许你将组件的DOM结构渲染到DOM树中的任何位置,而不仅仅是组件的根元素内部。这使得你可以更灵活地控制组件的渲染位置,实现一些复杂的布局和交互效果。例如,你可以将模态框(Modal)的DOM结构渲染到
元素的末尾,以确保其始终位于页面的最顶层4、Vue 3中的Suspense是什么
Suspense是Vue 3中引入的另一个新特性,用于处理异步组件的加载过程。通过在Suspense组件中包裹异步组件,并提供一个fallback选项,你可以在异步组件加载过程中展示一个占位符或加载指示器,直到异步组件加载完成并成功渲染。这使得你可以更优雅地处理异步组件的加载和错误情况,提升用户体验
- 通过
#default
插槽显示异步组件的内容。 - 通过
#fallback
插槽显示加载中的备用内容。
5、Vue3中的provide和inject是如何工作的
父组件中提供数据,并在子组件中注入这些数据,从而实现了组件之间的数据传递。简单来说就是父组件向子组件传值的一个方式。
6、Vue3中Fragment是什么
Fragment 是一种容器,它允许我们在 Vue 组件中返回多个根节点。通常情况下,Vue 组件只能有一个根元素,这在某些情况下可能会导致额外的 HTML 被渲染,或者需要使用额外的包裹元素。这种设计在 Vue 2 中是一个限制。而在 Vue 3 中,Fragment 功能被引入,允许开发者能够在一个组件中返回多个并列的元素,而不需要额外的 DOM 节点来作为容器。
7、ref和reactive区别
数据类型的处理
**
ref
**:- 可以包装任意类型的值(基本类型、对象、数组等)。
- 基本类型(如
string
,number
,boolean
)会被自动包裹成响应式对象。 - 对象或数组会被深层转换为响应式。
javascriptconst count = ref(0); // 基本类型 const user = ref({ name: "Alice", age: 30 }); // 对象
**
reactive
**:- 仅接受对象类型(
Object
,Array
,Map
,Set
等)。 - 直接返回对象的响应式代理(Proxy)。
javascriptconst state = reactive({ count: 0, user: { name: "Alice" } });
- 仅接受对象类型(
访问方式
**
ref
**:- 需要通过
.value
访问或修改值(在<template>
中自动解包,无需.value
)。
javascriptconst count = ref(0); console.log(count.value); // 0 count.value = 1;
- 需要通过
**
reactive
**:- 直接访问和修改属性。
javascriptconst state = reactive({ count: 0 }); console.log(state.count); // 0 state.count = 1;
深层响应式
**
ref
**:- 如果包裹的是对象或数组,会通过
reactive
自动递归转换为深层响应式。
javascriptconst nested = ref({ a: { b: 1 } }); nested.value.a.b = 2; // 响应式更新
- 如果包裹的是对象或数组,会通过
**
reactive
**:- 默认递归代理所有嵌套对象,所有层级都是响应式的。
javascriptconst state = reactive({ a: { b: 1 } }); state.a.b = 2; // 响应式更新
适用场景
**
ref
**:- 适合基本类型(如数字、字符串)。
- 需要明确引用一个响应式变量时(例如在组合式函数中返回)。
- 需要灵活传递响应式数据(通过
.value
保持引用)。
javascript// 组合式函数示例 function useCounter() { const count = ref(0); const increment = () => count.value++; return { count, increment }; }
**
reactive
**:- 适合复杂对象或状态树(如表单数据、全局状态)。
- 需要直接操作属性,避免频繁使用
.value
。
javascript// 复杂状态管理 const formState = reactive({ username: "", password: "", errors: {}, });
解构与响应式丢失
**
ref
**:- 解构时会保留响应性,因为
.value
的引用始终不变。
javascriptconst { value: count } = ref(0); // 仍响应式
- 解构时会保留响应性,因为
**
reactive
**:- 直接解构会丢失响应性!需配合
toRefs
或toRef
保持响应式。
javascriptconst state = reactive({ count: 0 }); const { count } = state; // ❌ 解构后失去响应性 const { count } = toRefs(state); // ✅ 保持响应性
- 直接解构会丢失响应性!需配合
底层实现
**
ref
**:- 通过一个包装对象(
RefImpl
)实现,内部用getter
/setter
拦截.value
操作。 - 如果值是对象,则调用
reactive
进行深层响应式转换。
- 通过一个包装对象(
**
reactive
**:- 基于 ES6 的
Proxy
实现,代理整个对象的读写操作。
- 基于 ES6 的
总结对比表
特性 | **ref ** | **reactive ** |
---|---|---|
接受的类型 | 任意类型 | 仅对象类型(Object , Array 等) |
访问方式 | .value (模板中自动解包) | 直接访问属性 |
深层响应式 | 自动深层转换 | 自动深层代理 |
适用场景 | 基本类型、需要灵活引用的数据 | 复杂对象、状态树 |
解构响应式 | 保留响应式 | 需toRefs 辅助 |
底层实现 | getter /setter + reactive | Proxy |
如何选择?
使用
ref
当:- 需要包装基本类型。
- 需要明确引用一个变量(例如在组合式函数中返回)。
- 需要灵活传递响应式数据(通过
.value
)。
使用
reactive
当:- 管理复杂对象或嵌套状态。
- 希望直接操作属性,避免
.value
8、watch和watchEffect区别
watch是显式声明依赖,需要指定具体的监听目标
watchEffect是自动收集依赖,回调函数中的响应式变量会被自动追踪
watch:惰性执行(可以设置immediate:true)、可获取旧值、支持深度监听对象\数组(deep:true)、控制监听时机(flush:pre|post|sync)
watchEffect:立即执行、无旧值、自动深度监听
对比总结
特性 | **watch ** | **watchEffect ** |
---|---|---|
依赖声明 | 显式指定依赖 | 自动收集回调内的依赖 |
执行时机 | 默认惰性(可配置immediate ) | 立即执行 |
旧值访问 | ✅(newVal, oldVal) | ❌ |
深度监听 | 需deep: true | 自动深度监听 |
适用场景 | 精确监听、对比新旧值、条件性逻辑 | 多依赖副作用、简化代码、立即响应 |
副作用清理 | 通过onCleanup | 通过onInvalidate |
如何选择
用watch
- 需要明确知道哪个数据变化触发了回调
- 需要旧值参与逻辑
- 需要条件性监听或延迟执行
用watchEffect
- 逻辑依赖多个响应式变量、且关系紧密
- 希望简化代码、减少手动声明依赖
- 需要立即执行并响应后续变化(如初始化加载)
9、Vue3如何优化性能
减少响应式依赖
- 避免过度响应式化:对无需响应式的数据使用shallowRef或shallowReactive(浅层相应)文档
- 冻结大数据:对只读的数据(如配置表)使用object.freeze()或markRaw阻止响应式转换
优化渲染性能
- v-once: 渲染静态内容一次,避免后续更新
- v-memo:缓存模板片段,仅在依赖项变化时重新渲染
- 合理使用key,提供唯一且稳定的key,帮助虚拟DOM高效复用节点
组件优化
- 异步组件(async Components)、使用defineAsyncComponent
- keepAlive缓存、缓存组件实例、避免重复渲染
防抖节流
10、Vue3如何处理组件的异步加载
使用defineAsyncComponent定义异步组件
import { defineAsyncComponent } from 'vue';
// 基本用法
const AsyncComponent = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
);
// 在组件中注册
export default {
components: {
AsyncComponent
}
}
defineAsyncComponent 支持配置加载中、错误、超时状态组件
const AsyncComponent = defineAsyncComponent({
loader: () => import('./components/AsyncComponent.vue'), // 动态导入
loadingComponent: LoadingSpinner, // 加载中的占位组件
errorComponent: ErrorDisplay, // 加载错误的提示组件
delay: 200, // 延迟显示 loading 的时间(避免闪烁)
timeout: 3000 // 超时时间(超时后显示 errorComponent)
});
11、Vue2和Vue3响应式的区别以及为什么3比2好
vue2的底层技术是基于ES5的Object.defineProperty、vue3的底层技术是基于ES6的Proxy
vue2响应式的瓶颈
无法监听动态属性
- 对象属性:通过vue.set或vue.delete手动触发更新
- 数组索引:直接通过下标修改数组不会触发更新
性能瓶颈
- 初始化递归遍历:大象对象或深层嵌套数据初始化耗时
- 全量依赖收集:每次数据变化需要遍历所有依赖,效率低
vue3对响应式的核心优化
数据监听
- 动态属性:无需set\delete,直接响应新增或者删除的属性
- 数组索引修改:vue2无法监听到通过下标修改数据
高性能
- 惰性代理:仅在对象访问的时候创建代理、减少初始化开销
- 精准更新:基于Proxy的依赖追踪,避免vue2中全量对比的开销