Vue3 相比 Vue2 有哪些新特性?Composition API 好在哪?

2026年 阅读约 9 分钟 面试指南 · 前端面试

Vue3新特性面试题全覆盖:Composition API优势、script setup语法糖、Proxy响应式、Fragment/Teleport/Suspense三大新组件、静态提升等性能优化、破坏性变更清单。

一句话总结

Vue3 的升级是四个方向:响应式(defineProperty → Proxy,彻底解决增删监听)、代码组织(Options API → Composition API,逻辑按功能聚合、可抽 composable 复用)、性能(静态提升、PatchFlags、Block Tree、事件缓存,虚拟 DOM 只 diff 动态内容)、生态(Fragment/Teleport/Suspense、完整 TS 支持、Tree-shaking 减体积)。

初级理解

Composition API 的核心动机:Options API 把一个功能拆散在 data/methods/computed/watch 各处("跳来跳去写代码"),逻辑复用靠 mixin(来源不明、命名冲突)。Composition API 让同一个功能写在一起,复用就是一个普通函数。

<script setup> // 搜索功能的全部逻辑聚合在一起 const keyword = ref('') const list = ref([]) const loading = ref(false) async function search() { loading.value = true; list.value = await api.search(keyword.value); loading.value = false } watch(keyword, debounce(search, 300)) onMounted(search)</script>

三大新组件:

  • Fragment:模板支持多根节点(Vue2 必须单根)
  • Teleport:把子树渲染到 DOM 任意位置——弹窗/抽屉传到 body 下,彻底解决父级 overflow/z-index 裁剪问题
  • Suspense:等待异步组件(含 setup async)时渲染 fallback 加载态
<Teleport to="body"> <div class="modal">我渲染在 body 下,但逻辑仍属于组件树</div> </Teleport>

中级深入

<script setup> 语法糖(面试必问):顶层声明的变量/函数/导入自动暴露给模板,组件导入即注册(无需 components 选项),编译产物更少、运行更快;内置宏 defineProps/defineEmits/defineExpose/defineModel/defineOptions 无需导入、编译期处理。

<script setup> import MyChart from './MyChart.vue' // 直接用,不用注册 const props = defineProps({ id: Number }) const emit = defineEmits(['save']) defineOptions({ name: 'UserDetail' }) // 组件名(keep-alive/递归组件用) </script>

生命周期映射:setup 替代 beforeCreate/created;onMounted/onUpdated/onUnmounted/onBeforeUnmount 等一一对应;新增 onErrorCaptured 的组合式写法;keep-alive 组件的 onActivated/onDeactivated。

性能提升点(中级要能说全):① Proxy 懒代理,初始化不再递归全量劫持;② 静态提升:静态 vnode 只创建一次;③ PatchFlags + Block Tree:diff 只遍历动态节点、只比动态属性;④ 事件缓存:inline handler 不再每次渲染重建;⑤ Tree-shaking:没用的内置组件/指令(Transition、v-model 修饰符等)打包时被裁剪,体积更小。

v-model 变化:Vue2 默认 value/input 且一个组件只能一个;Vue3 改为 modelValue / update:modelValue,支持 v-model:title 多个双向绑定。

高级拓展

编译优化细节(拉开差距的部分):PatchFlag 是位标记,如 1 /* TEXT */、2 /* CLASS */、8 /* PROPS */——diff 时判断 flag:只有 TEXT 就只比 children 文本;PROPS 则附带动态属性名数组,精确到字段。Block 收集动态后代节点时"拍平"了层级,无论嵌套多深,更新都只走一条扁平列表。

// 编译产物:props flag 8 + 动态属性名数组 ["id"] _createElementVNode('div', { id: _ctx.uid }, null, 8 /* PROPS */, ["id"])

主要破坏性变更(迁移必查清单):

  • 响应式系统换 Proxy → 移除 $set/$delete/Vue.set、Vue.observable(换 reactive)
  • 移除 $on/$off/$once(事件总线)、filters(用计算属性/方法)、函数式组件语法、$children
  • 异步组件改 defineAsyncComponent;v-if/v-for 优先级反转;scoped 样式深度选择器 ::v-deep → :deep()
  • 全局 API 改实例化:Vue.use/global properties → app.use/app.config.globalProperties

TS 支持:源码用 TS 重写只是基础,关键是 defineProps<{ id: number }>() 类型即校验、ref/computed 全链路类型推导、模板表达式在 IDE 里有完整类型提示(Volar)。

面试加分项:被问"Composition API 会不会取代 Options API"时,答"不会——官方说 Options API 仍是首选的入门方式且继续维护,两者共存;Composition API 解决的是大型项目逻辑组织与复用问题",体现你理解设计取舍而非站队。

实战场景

场景一:mixin 迁移为 composable

// Vue2:listMixin 里 data/methods/created 混入,组件里看不出 list 哪来的 // Vue3: export function useList(fetcher) { const list = ref([]), loading = ref(false) const reload = async () => { loading.value = true; list.value = await fetcher(); loading.value = false } onMounted(reload) return { list, loading, reload } // 来源、命名一目了然,可实例化多次 }

场景二:弹窗被父级 overflow 裁剪

// Vue2 时代只能把弹窗挪到最外层或调 z-index 赌运气 // Vue3:Teleport 直送 body,样式与逻辑都不用动 <Teleport to="body"> <el-dialog v-model="visible" /> </Teleport>

场景三:Vue2 项目平滑升级

// 官方路径:先升 Vue 2.7(内置 Composition API + script setup 支持) // 用 @vue/compat 兼容层渐进迁移 → 处理编译器警告 → 切 Vue3 // 配合 gogocode / vue-codemod 自动转换 filters、$on 等常见用法

面试模拟

Q:Composition API 和 Options API 的本质区别?

A:组织维度不同——Options 按选项类型切割代码(一个功能分散四处),Composition 按逻辑功能聚合(一个功能收拢在一个作用域内),且复用单元从"隐式混入"变成"显式函数"。运行时性能没有本质差异,核心收益在可维护性和逻辑复用;大型组件还能避免 this 指向的心智负担(setup 里没有 this)。

Q:Vue3 快在哪里?请从编译和运行时两个角度说。

A:编译时——静态提升、PatchFlags、Block Tree、事件缓存,把"哪些是动态的"提前算好;运行时——Proxy 懒代理让初始化开销从 O(属性总数) 变为 O(访问到的属性),diff 只处理动态节点集合。加上 Tree-shaking 减小包体积、更快解析(与 Proxy 无关但编译器输出精简),官方数据渲染更新性能有 1.3~2 倍提升、内存占用显著降低。

Q:defineExpose 是干什么的?为什么需要它?

A:script setup 的组件默认是"封闭"的——外部通过模板 ref 拿到实例也访问不到内部绑定(Vue3 显式关闭了隐式暴露,避免脆弱耦合)。defineExpose({ reset, focus }) 白名单式声明哪些方法/状态可被父组件调用,这是把"默认全开"改为"默认关闭 + 显式声明"的 API 设计。