一、基础与核心差异
- Vue3 相比 Vue2 有哪些核心改进?
- 响应式系统:基于
Proxy实现,支持动态新增/删除属性、数组索引修改,无需$set;Vue2 基于Object.defineProperty,有较多限制。 - API 风格:主推组合式 API(
setup、ref等),按功能聚合逻辑;Vue2 用选项式 API,逻辑分散在多个选项中。 - 性能优化:编译时优化(静态提升、PatchFlags)、响应式懒代理,渲染性能提升约55%。
- 生态升级:支持 TypeScript 原生集成,路由(Vue Router 4)、状态管理(Pinia)配套更新,移除
filter、$on等冗余特性。
- 响应式系统:基于
- 选项式 API 与组合式 API 的区别及适用场景?
- 选项式 API:按
data、methods、mounted等选项分割逻辑,适合简单组件,上手快;但复杂组件中逻辑分散,复用困难。 - 组合式 API:用
setup和组合函数按功能聚合逻辑,适合复杂组件,逻辑复用性强;学习成本稍高,但可维护性更好。 - 场景:小型项目/简单组件用选项式;中大型项目/复杂组件(如表单、数据看板)用组合式。
- 选项式 API:按
- Vue3 为什么移除了
filter、$on/$off等特性?filter:功能可被computed或方法替代,且filter不支持参数类型推断,与组合式 API 风格不符。$on/$off(事件总线):易导致全局事件混乱,难以追踪;推荐用第三方库(如mitt)或 Pinia 实现跨组件通信,更可控。
- Vue3 的
template语法有哪些新增特性?- 多根节点:组件模板可直接包含多个根节点(无需外层包裹
div)。 - 动态指令参数:支持动态绑定指令参数(如
v-bind:[key]="value"、v-on:[event]="handler")。 - 片段(Fragments):减少不必要的 DOM 嵌套,优化渲染性能。
- 更好的 TypeScript 支持:模板表达式类型校验更严格。
- 多根节点:组件模板可直接包含多个根节点(无需外层包裹
- Vue3 对 TypeScript 的支持做了哪些优化?
- 核心代码用 TypeScript 重写,类型定义更完善。
- 组合式 API 中变量/函数为显式局部变量,TypeScript 可直接推断类型,无需额外声明。
defineProps/defineEmits支持泛型定义,类型校验更精准(如defineProps<{ name: string }>())。- 模板中表达式的类型错误可在编译时被捕获。
二、响应式系统
ref、reactive、toRef、toRefs的区别及使用场景?ref:包装基础类型(number/string等)为响应式,也可包装对象;需通过.value访问,模板中自动解包。场景:基础类型、需单独维护的响应式变量。reactive:将对象/数组转为响应式(深层代理),直接访问属性(无需.value)。场景:复杂对象/数组(如user: { name, age })。toRef:为reactive对象的单个属性创建ref(保持响应性)。场景:需单独传递对象的某个属性(如const ageRef = toRef(user, 'age'))。toRefs:将reactive对象转为ref集合(所有属性变为ref)。场景:解构reactive对象时避免丢失响应性(如const { name, age } = toRefs(user))。
- Vue3 响应式基于 Proxy,相比 Vue2 的 Object.defineProperty 有哪些优势?
- 监听范围更广:Proxy 代理整个对象,支持动态新增/删除属性(如
obj.newKey = 1、delete obj.key),无需$set;Object.defineProperty只能监听初始化时的属性。 - 数组支持更完善:Proxy 可拦截数组索引修改(
arr[0] = 1)、长度修改(arr.length = 0);Object.defineProperty需重写数组原型方法(push/splice等),有局限性。 - 性能更优:Proxy 采用“懒代理”,访问嵌套对象时才递归代理,初始化性能更好;
Object.defineProperty需递归遍历所有属性,损耗大。 - 拦截器更全面:支持
deleteProperty、has等13种拦截操作,覆盖更多场景。
- 监听范围更广:Proxy 代理整个对象,支持动态新增/删除属性(如
- 什么情况下响应式会失效?如何解决?
- 直接替换响应式对象:
let obj = reactive({ a: 1 }); obj = { b: 2 }(原代理对象被替换,新对象非响应式)。解决:用ref包装(obj.value = { b: 2 })。 - 通过索引修改数组长度:
const arr = reactive([1,2]); arr.length = 0(Vue3 已支持,低版本可能失效)。解决:用splice(arr.splice(0))。 - 解构
reactive对象:const { a } = reactive({ a: 1 })(a变为普通值,非响应式)。解决:用toRefs解构(const { a } = toRefs(...))。
- 直接替换响应式对象:
shallowRef、shallowReactive、markRaw的作用及使用场景?shallowRef:浅层响应式,仅.value变化触发更新(内部对象修改不触发)。场景:无需深层响应的大对象(如图表配置)。shallowReactive:浅层响应式,仅顶层属性变化触发更新(嵌套对象修改不触发)。场景:已知深层数据不会变化的对象(如静态配置)。markRaw:标记对象为“非响应式”,避免被reactive/ref包装。场景:第三方库实例(如echarts实例)、无需响应式的纯数据。
ref的.value设计原理是什么?为什么模板中不需要.value?- 原理:
ref通过包装对象({ value: 原始值 })实现响应式(基础类型无法被 Proxy 直接代理),.value是访问原始值的入口。 - 模板自动解包:Vue3 编译模板时,会自动识别
ref变量,在渲染时隐式添加.value(如{{ count }}等价于count.value),简化写法。
- 原理:
- 如何手动触发响应式更新?
triggerRef(ref):手动触发shallowRef的更新(当修改其内部对象时)。componentInstance.$forceUpdate():强制组件重新渲染(不推荐,可能导致性能问题)。- 替换
ref的.value:ref.value = { ...ref.value }(触发set拦截器)。
三、组合式 API 与生命周期
setup的执行时机?为什么没有this?- 执行时机:在
beforeCreate之前执行,此时组件实例未初始化,data、methods、props等尚未挂载。 - 无
this的原因:- 避免
this指向模糊(Vue2 中this常因嵌套函数丢失上下文)。 - 促进逻辑复用(组合函数无需依赖组件实例,可独立运行)。
- 更好支持 TypeScript(
this类型难以自动推断,显式变量更易类型化)。
- 避免
- 执行时机:在
- Vue3 生命周期钩子与 Vue2 如何对应?
onRenderTracked有什么用?- 对应关系:
onBeforeMount→beforeMountonMounted→mountedonBeforeUpdate→beforeUpdateonUpdated→updatedonBeforeUnmount→beforeDestroyonUnmounted→destroyed
onRenderTracked:调试用,当组件渲染过程中收集依赖时触发,可查看哪些响应式数据被追踪。
- 对应关系:
watch与watchEffect的区别?如何监听reactive对象的某个属性?- 区别:
watch需明确指定监听源(如ref/reactive属性),可获取新旧值,支持deep/immediate。watchEffect自动追踪依赖,无需指定源,初始化时立即执行,无法获取旧值。
- 监听
reactive对象的单个属性:需用 getter 函数(避免监听整个对象):
watch(() => obj.name, (newVal) => { ... })- 区别:
computed的缓存机制?如何实现可写的computed?- 缓存机制:
computed会缓存计算结果,仅当依赖的响应式数据变化时,才重新计算;多次访问相同值时直接返回缓存。 - 可写
computed:通过get/set配置实现:
const fullName = computed({ get: () => `${first.value} ${last.value}`, set: (val) => { const [f, l] = val.split(' '); first.value = f; last.value = l; } });- 缓存机制:
- 组合函数(Composables)的设计原则?如何封装一个复用性高的组合函数?
- 设计原则:
- 单一职责:一个组合函数只处理一个功能(如
useFetch仅处理请求)。 - 显式输入输出:通过参数接收依赖,通过返回值暴露状态/方法,不依赖
this。 - 无副作用:内部状态不污染全局,清理逻辑需完善(如
onBeforeUnmount取消请求)。
- 单一职责:一个组合函数只处理一个功能(如
- 封装示例:
// useFetch.js export function useFetch(url) { const data = ref(null); const loading = ref(true); const fetchData = async () => { loading.value = true; data.value = await axios.get(url); loading.value = false; }; fetchData(); onBeforeUnmount(() => { /* 取消请求 */ }); return { data, loading, fetchData }; } - 设计原则:
<script setup>相比普通setup函数有哪些语法简化?- 无需显式定义
setup函数,顶层代码自动在setup中执行。 - 无需
return,顶层变量、函数、导入内容自动暴露给模板。 - 内置
defineProps/defineEmits/defineExpose宏,无需导入。 - 支持直接导入组件并使用(无需在
components选项注册)。
- 无需显式定义
四、组件通信与状态管理
- Vue3 中父子组件通信的方式有哪些?
defineProps/defineEmits如何使用?- 方式:
props+emit、v-model、ref访问子组件、provide/inject(跨代)。 defineProps(子组件接收props):
<script setup> const props = defineProps({ name: String, age: { type: Number, default: 0 } }); </script>defineEmits(子组件触发事件):
<script setup> const emit = defineEmits(['update', 'delete']); const handleClick = () => emit('update', 'newVal'); </script> - 方式:
v-model在 Vue3 中的变化?如何实现多值绑定和自定义修饰符?- 变化:Vue3 中
v-model等价于:modelValue+@update:modelValue(替代 Vue2 的:value+@input)。 - 多值绑定:通过
v-model:propName绑定多个值:
<Child v-model:name="name" v-model:age="age" />- 自定义修饰符:子组件通过
props.modelModifiers接收修饰符:
<!-- 父组件 --> <Child v-model.capitalize="name" /> <!-- 子组件 --> <script setup> const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) } }); const emit = defineEmits(['update:modelValue']); // 使用修饰符:if (props.modelModifiers.capitalize) 处理大写 </script>- 变化:Vue3 中
provide/inject如何实现响应式传递?使用时需要注意什么?- 响应式传递:
provide传递ref/reactive对象,inject接收后可直接响应变化:
// 父组件 const theme = ref('light'); provide('theme', theme); // 子组件 const theme = inject('theme'); // 响应式- 注意事项:
- 避免在子组件中直接修改
inject的值(破坏单向数据流),应通过emit通知父组件修改。 - 需指定默认值时,用
inject('key', defaultValue)。
- 避免在子组件中直接修改
- 响应式传递:
- Pinia 相比 Vuex 有哪些优势?如何用 Pinia 实现模块化状态管理?
- 优势:
- 无需
mutations,直接通过actions修改状态(简化代码)。 - 移除
modules,用defineStore定义独立仓库,天然支持模块化。 - 原生支持 TypeScript,类型推断更友好。 - 支持热更新和调试工具集成。
- 无需
- 模块化示例:
// stores/user.js import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ name: '张三' }), actions: { setName(newName) { this.name = newName; } } }); // 组件中使用 const userStore = useUserStore(); - 优势:
- 非父子组件通信有哪些方案?
- 事件总线:用
mitt库(Vue3 移除了$on/$off),适合简单场景。 provide/inject:适合祖孙组件,跨层级传递数据。- Pinia:全局状态管理,适合复杂应用(多组件共享状态)。
- 路由参数:通过 URL 传递数据(适合页面级组件)。
- 事件总线:用
五、性能优化与实战
- Vue3 做了哪些编译时优化?
- 静态提升:将静态节点/属性(如
<div class="static">)提升到渲染函数外,避免每次渲染重新创建。 - PatchFlags:为动态节点添加标记(如
TEXT/CLASS),更新时只遍历带标记的节点,减少对比开销。 - 缓存事件处理函数:
@click="handleClick"会缓存函数引用,避免每次渲染创建新函数导致子组件更新。 - 预字符串化:连续静态节点会被合并为字符串,减少虚拟 DOM 创建成本。
- 静态提升:将静态节点/属性(如
- 如何避免组件不必要的重渲染?
- 用
shallowRef/shallowReactive处理无需深层响应的数据。 - 用
memo包装组件,指定依赖项(仅依赖变化时重新渲染):<script setup> import { memo } from 'vue'; const MyComponent = memo( ({ prop1 }) => <div>{prop1}</div>, (prev, next) => prev.prop1 === next.prop1 ); </script> - 避免在模板中使用匿名函数(如
@click="() => {}"),会导致每次渲染创建新函数。 - 用
toRef/toRefs解构reactive对象,保持响应性的同时减少重渲染触发。
- 用
- 大列表渲染如何优化?
- 虚拟滚动:用
vue-virtual-scroller等库,只渲染可视区域内的列表项(适合1000+数据)。 v-for加key:key需唯一且稳定(避免用索引),帮助 Vue 精准复用节点。- 列表分页:分段加载数据,减少单次渲染压力。
- 冻结静态内容:用
Object.freeze冻结不变化的数据,避免响应式追踪开销。
- 虚拟滚动:用
- 如何在 Vue3 中实现按需加载组件?
- 异步组件:用
defineAsyncComponent定义,配合import()动态加载:
import { defineAsyncComponent } from 'vue'; const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));Suspense配合:在等待异步组件加载时显示占位内容:
<Suspense> <template #default><AsyncComponent /></template> <template #fallback>加载中...</template> </Suspense> - 异步组件:用
六、高级特性与生态
Teleport和Suspense的使用场景?Teleport:将组件内容渲染到指定 DOM 节点(如body),解决嵌套组件的样式隔离问题(如弹窗、模态框避免被父组件overflow: hidden截断):
<Teleport to="body"><div class="modal">弹窗</div></Teleport>Suspense:配合异步组件或async setup,在等待异步操作(如接口请求)时显示加载状态,简化异步逻辑(注意:Suspense仍为实验性特性,生产环境需谨慎)。
- Vue Router 4 相比 Vue Router 3 有哪些变化?
- 支持组合式 API:新增
useRouter/useRoute钩子,替代this.$router/this.$route。 - 路由守卫可直接使用组合式 API(如
onBeforeRouteLeave)。 - 移除
mode选项,用createWebHistory/createWebHashHistory替代。 - 支持路由组件懒加载的更简洁写法:
component: () => import('./Page.vue')。
- 支持组合式 API:新增
- 如何在 Vue3 中集成单元测试?
- 用
Vitest作为测试框架(Vue 官方推荐,速度快,支持 ESM)。 - 配合
@vue/test-utils测试组件:
import { mount } from '@vue/test-utils'; import MyComponent from './MyComponent.vue'; test('renders props.name', () => { const wrapper = mount(MyComponent, { props: { name: 'test' } }); expect(wrapper.text()).toContain('test'); }); - 用
- Vue2 项目迁移到 Vue3 的核心步骤及注意事项?
- 依赖升级:Vue 升级到 3.x,Vue Router 升级到 4.x,用 Pinia 替代 Vuex。
- 语法迁移:
data函数 →ref/reactive。- 生命周期钩子 → 组合式 API 钩子(如
mounted→onMounted)。 this.xxx→ 直接访问变量,this.$emit→defineEmits。
- 移除特性处理:
filter用computed替代,$children用ref获取子组件。 - 兼容性:第三方库需支持 Vue3(如
element-plus替代element-ui),IE 浏览器不再支持(Vue3 基于 ES6+)。