组件与组合式 API

共 23 题
📑 题目列表 23 题
#
★★★

1. defineModel 的多 v-model 绑定(defineModel('a')、defineModel('b'))

defineModel 如何支持多个 v-model 绑定?defineModel('a') 与 defineModel('b') 的机制与工程价值是什么?

  • defineModel() 编译为 modelValue prop + update:modelValue emit 的简写
  • 命名 v-model(defineModel('a'))对应 a prop 与 update:a emit
  • 双向绑定语法糖在子组件内的简化与类型安全

defineModel 是 Vue 3.4 引入的编译宏,把"声明 prop + 声明 emit + 读写 ref"三件事压缩为一个响应式 ref:默认的 defineModel() 对应父组件 v-model 的 modelValue prop 与 update:modelValue 事件;defineModel('a')、defineModel('b') 则声明命名模型,分别对应 v-model:a、v-model:b 的绑定,编译为 a/b prop 与 update:a/update:b emit。子组件内部直接读写该 ref(如 value.value = x),赋值会自动触发对应的 update 事件通知父组件,实现真正的双向绑定且天然类型安全——prop 类型与"是否必填、默认值"可通过 defineModel 的参数配置。多个命名模型让一个组件同时受控多个维度(如分页组件的 page 与 pageSize),避免为每个维度手写 prop/emit 样板代码。

理解 defineModel 的关键是"编译宏 + 语法糖":它没有新增运行时机制,只是把既有 prop/emit 模式自动化,并让"子组件内读写如 ref"的体验与单向数据流原则兼容(对 ref 赋值本质是派发 update 事件)。回答时建议强调"默认模型 vs 命名模型"的对应关系,以及可选的 required/default/type 配置,说明其对组件库 API 设计的简化价值。

<!-- 子组件 -->
<script setup>
const page = defineModel('page', { type: Number, required: true })
const pageSize = defineModel('pageSize', { type: Number, default: 20 })
</script>
<!-- 父组件:<Pagination v-model:page="p" v-model:page-size="size" /> -->
#
★★★

2. useTemplateRef() 与 useId() 在 SSR 与 Composition API 中的应用

useTemplateRef() 与 useId() 在 SSR 与组合式 API 中分别解决什么问题?如何配合使用?

  • useTemplateRef() 声明式获取模板引用,替代字符串 ref
  • useId() 生成 SSR 一致的确定性 ID
  • 两者在表单关联、动画、组件库中的协作场景

useTemplateRef() 是 Vue 3.5 的组合式 API:通过 useTemplateRef('el') 声明一个与模板中 ref="el" 绑定的 ref,组件挂载后自动填充 DOM 元素或组件实例,卸载时置空;相比传统字符串 ref 或"回调 + 手动变量"方式,它更类型安全、可显式判断空值,且解决了"同一变量名与模板 ref 名重复声明"的歧义。useId() 则为标签与控件的 for/id 关联提供 SSR 确定性 ID。二者常在表单场景配合:useId() 生成 id,useTemplateRef() 获取控件 DOM 做焦点管理或尺寸测量。SSR 下 useId 保证服务端/客户端 ID 一致避免水合警告,useTemplateRef 在服务端返回空 ref、只在客户端挂载后生效,因此依赖其值的逻辑须放入 onMounted 或 watch 回调。

回答重点是"声明式、类型安全的模板引用"与"确定性 ID"各自的价值,以及二者在 SSR 下的行为边界:useId 是渲染期可用的确定性值,useTemplateRef 是挂载期才填充的运行时值。能指出 useTemplateRef 返回的 ref 在模板中作为 ref 属性值使用(ref="el" 中的字符串需与声明名一致)即为掌握。

#
★★★

3. Vue 3 Teleport 与 KeepAlive 在 Modal/Tabs 缓存的工程价值

Teleport 与 KeepAlive 在 Modal 与 Tabs 场景中各自解决什么问题?两者协作有哪些工程价值与边界?

  • Teleport 将内容渲染到指定 DOM 位置(body),脱离父级 overflow/层级约束
  • KeepAlive 缓存组件实例,避免切换时销毁重建
  • Modal 中 Teleport 保证遮罩层级与事件冒泡正确

Teleport 解决"渲染位置"问题:to 指定目标容器(通常是 body),使 Modal 的遮罩与弹层脱离父级 overflow: hidden、transform 等布局约束,正确显示在页面顶层,并配合 Transition 实现平滑动画;事件仍沿真实 DOM 树冒泡到 Vue 组件树,逻辑归属不变。KeepAlive 解决"实例缓存"问题:包裹动态组件或 router-view 时,组件切换不销毁而是缓存(可配置 include/exclude/max),重新显示时恢复状态——Tabs 场景下能保留每个标签页的表单输入、滚动位置与请求数据,避免重复初始化。工程价值:Modal 用 Teleport 保证层级与可用性,Tabs 用 KeepAlive 保状态提性能;协作边界是缓存组件内部的 Teleport 内容(如弹层)在缓存期间仍在 DOM 中,需注意其可见性与重复 id;KeepAlive 缓存键与 Teleport 目标变化时需按业务显式管理。

本题考察两个"位置/生命周期"类内置组件的能力边界:Teleport 管 DOM 归属,KeepAlive 管实例生命周期。回答时分别说明典型场景(Modal 层级、Tabs 状态保持),再谈协作边界(缓存内 Teleport 的 DOM 驻留、activated/deactivated 钩子、cache key),体现对"缓存 + 传送"组合的工程认知。

#
★★★

4. Vue 3.5 defineModel/useModel 的双向绑定在表单组件的现代取舍

Vue 3.5 中 defineModel 与 useModel 如何实现表单组件的双向绑定?现代实践中的取舍是什么?

  • defineModel 编译宏在 SFC 中的用法
  • useModel() 运行时 API 在非 SFC/复杂场景的补充
  • 双向绑定与单向数据流的一致性

defineModel 让表单子组件以"本地 ref"体验实现双向绑定:子组件内 const model = defineModel({ type: String }) 即可读写,父组件通过 v-model 绑定;其实现是 prop + emit 的语法糖,内部还处理了"仅修改时触发"等细节。Vue 3.5 的 useModel() 是运行时版本,可用于非 SFC 场景或在组件库中需要编程式获取模型绑定的地方,语义与 defineModel 一致。现代取舍:优先 defineModel 简化声明;对复杂表单组件(多字段、校验、格式化输入)建议仍采用"受控组件"模式——props 驱动展示、update 事件回传,把格式化/校验逻辑收敛在子组件,父组件保持纯数据状态;对高频输入需结合防抖避免过度触发 update。defineModel 还支持 required、type、默认值等 prop 配置,保证类型安全与文档化。

本题考察"语法糖的本质"与"受控思维":defineModel/useModel 不改变单向数据流,只是减少样板;取舍的核心是——简单绑定用 defineModel,复杂校验/格式化用显式受控 + 派生。回答时点明"子组件内对 model.value 赋值 = 派发 update 事件",并给出受控模式的边界(格式化、异步校验、多字段联动)。

#
★★★

5. defineSlots 与具名插槽的类型推导

defineSlots 如何为具名插槽提供类型推导?在组件库与类型安全方面有什么价值?

  • defineSlots 声明插槽名称与插槽 props 的类型
  • 模板中 v-slot 的自动补全与类型检查
  • 与 useSlots() 运行时取值的配合

defineSlots 是 SFC 编译宏,用于声明组件对外插槽的契约:const slots = defineSlots<{ header?: (props: { title: string }) => any; default: () => any }>()。它不产生运行时开销(编译期剥离,仅在类型层面生效),但为模板中的 使用、父组件 v-slot 绑定的 props 提供完整类型推导与自动补全;结合 useSlots() 可在脚本中类型安全地读取插槽函数。作用域插槽的 props 类型写在函数参数上,具名插槽通过键名区分,可选插槽用 ?: 标记。对组件库而言,defineSlots 与 defineProps/defineEmits 一起构成组件完整的类型契约,显著提升使用方的开发体验。

本题考察组件类型契约的完整性:props/emits 定义数据进出,slots 定义内容分发。defineSlots 的价值在"编译期类型 + 零运行时成本",回答时建议给出具名与作用域插槽的类型写法,并说明与 useSlots() 的配合(运行时读取 + 类型约束),体现对"类型即文档"的工程实践认知。

#
★★★

6. 组合式函数(composables)的复用模式与 use* 命名规范

组合式函数(composables)的典型复用模式是什么?use* 命名规范与组织方式有哪些约定?

  • composables 封装"有状态的逻辑":响应式状态 + 生命周期 + 副作用
  • use* 前缀约定标识可组合函数
  • 返回 ref 而非解构值,保持响应性

组合式函数把"有状态的逻辑"(响应式状态、watch、生命周期钩子、副作用)封装为可复用函数,命名以 use 开头(useMouse、useDebounce),这是 Vue 生态的硬性约定,便于识别与自动导入工具处理。核心复用模式:函数内部创建 ref/reactive 状态并返回(返回 ref 而非原始值,保证解构后响应性),在组件调用时挂载生命周期钩子与清理逻辑(onUnmounted 释放监听),支持参数化(接收响应式源或普通值并用 toRef 归一化)。组织约定:单一职责、返回对象按"状态/方法"分组命名、内部使用 watch/computed 表达派生逻辑、避免在函数外共享可变状态(除非是有意设计的全局单例)。composables 天然可测试(不依赖组件实例,可在 Vitest 中直接调用并断言状态变化),且通过嵌套组合构建更上层的业务能力。

本题考察组合式 API 的核心复用抽象。回答的要点是:use* 命名是生态约定、返回响应式引用保持解构安全、生命周期与清理封装在函数内部、单一职责与可组合性。能对比 mixins 的缺陷(命名冲突、来源不明、隐式共享)来说明 composables 的优势,体现对演进动机的理解。

#
★★★

7. defineProps/defineEmits/defineModel 的 props/emit/modelValue 类型安全

defineProps、defineEmits 与 defineModel 如何共同保障组件 props、事件与 v-model 的类型安全?

  • defineProps 的类型声明与运行时校验的对应关系
  • defineEmits 事件名与 payload 的类型契约
  • defineModel 复用 props/emit 的类型体系

defineProps 支持基于类型字面量(as const 对象)或 TS 接口声明 props,编译期生成类型并在运行时保留 prop 校验(通过 compiler 注入运行时 props 配置);defineEmits 声明事件名及 payload 类型,父组件绑定事件时获得参数类型检查,未声明的事件在模板中触发会报类型错误。defineModel 本质上是"modelValue prop + update:modelValue emit"的封装,因此自动继承这套类型体系:可传 type、required、default 配置,并在子组件读写时获得对应类型。withDefaults 为可选 props 提供默认值且类型完整推导。三者协作形成组件"数据进(props)、事件出(emits)、双向绑定(model)"的完整类型闭环,任何一端的类型不匹配都会在编译期或 IDE 中暴露。

本题考察 SFC 宏的类型机制:三个宏都是编译期声明、运行时配置的"双轨"——类型层面服务 TS 与 IDE,运行时层面服务 prop 校验。回答时强调"类型即契约":组件库借助它们提供零文档自解释的 API;同时注意运行时校验只在开发模式生效,生产构建会移除部分校验,类型安全仍靠编译期保证。

#
★★★

8. 组件生命周期(onMounted/onUpdated/onUnmounted)与清理函数

Vue 3 的 onMounted、onUpdated、onUnmounted 等生命周期钩子分别在什么时机执行?如何正确使用清理函数?

  • 各钩子的触发时机与执行顺序
  • setup 中同步调用注册钩子的约束
  • onUnmounted 中释放监听、定时器、订阅等资源

Vue 3 组合式 API 生命周期钩子:onBeforeMount/onMounted 在组件挂载前后触发(onMounted 后 DOM 可用,适合初始化第三方库、绑定事件、发起请求);onBeforeUpdate/onUpdated 在响应式变化导致 DOM 更新前后触发(onUpdated 中访问更新后的 DOM,但注意不要在更新钩子中修改状态造成循环);onBeforeUnmount/onUnmounted 在卸载前后触发,onUnmounted 是资源清理的标准位置——移除事件监听、clearInterval、关闭 WebSocket、销毁第三方实例、取消订阅。钩子必须在 setup 执行期间同步调用(编译宏/注册函数依赖当前组件实例上下文),异步回调中调用会失效。工程上常用 onMounted 创建 + onUnmounted 释放的配对模式,也可用 watch 的 stop、effectScope 或 onWatcherCleanup 承接更细粒度的清理。

本题考察生命周期的基础但关键的纪律:钩子注册的"同步上下文"约束、onMounted 与 onUpdated 的差异(首次挂载 vs 更新)、卸载清理的完备性(防内存泄漏)。回答时建议给出配对模式示例,并说明与响应式清理机制(watch 自动 stop、effectScope)如何分工,体现对生命周期管理的系统性理解。

#
★★★

9. Composition API 与 Options API 的场景与边界

Composition API 与 Options API 各自适合什么场景?它们的边界如何界定?

  • Options API 的声明式结构与中小组件/模板化团队的适应性
  • Composition API 的逻辑复用、类型推导与复杂组件组织
  • 二者可混用(setup 中调用 Options 钩子)

Options API 以 data/computed/methods/watch/生命周期等固定选项组织代码,结构直观、心智负担低,适合简单组件、模板化程度高的团队与快速开发场景;其局限是同一功能的代码分散在各选项中,组件复杂后"关注点散落",且逻辑复用依赖 mixins(有命名冲突、来源不明的缺陷)。Composition API 以"功能"为单位组织代码:把某个业务的状态、computed、watch、生命周期收敛为一个 composable 或一个代码块,支持精确的类型推导、轻松复用与重命名,适合复杂业务组件、大型项目与组件库。边界:两者并非互斥,SFC 中可在 setup 中调用组合式 API 的同时保留 Options 选项(混用合法);团队实践中通常"新代码用组合式、存量代码渐进迁移",并以 eslint 规则约束组织方式保证一致性。

回答应避免"组合式绝对优于选项式"的绝对化:从代码组织(按选项 vs 按功能)、复用(mixins vs composables)、类型(推导 vs 标注)三个维度对比,再给出迁移策略与混用规则。体现"根据复杂度与团队选型"的工程判断是本题加分点。

#
★★★

10. Vue 3 编译器在静态提升 hoisting、PatchFlag、Block Tree 优化的工程价值

Vue 3 编译器的静态提升、PatchFlag 与 Block Tree 优化分别解决什么问题?它们的工程价值是什么?

  • 静态提升:不变节点/属性提升到渲染函数外,避免重复创建 VNode
  • PatchFlag:标记动态节点类型,diff 时按位比较跳过静态内容
  • Block Tree:以动态节点为入口的树形追踪,动态子节点扁平化收集

静态提升(hoisting)把模板中不依赖响应式的节点与属性提升到渲染函数作用域外,每次渲染复用同一 VNode 对象,减少创建与比较开销;补丁标志(PatchFlag)由编译器为每个动态绑定标注优化类型(如 TEXT、CLASS、PROPS、STYLE、动态子节点),diff 时按标志精确比较,跳过无变化部分;Block Tree 将带动态节点的组件模板编译为"块",块内动态子节点(含 v-if/v-for 分支)被扁平化收集到 dynamicChildren 数组,更新时直接遍历该数组而无需递归整棵 vdom 树。三者叠加使 Vue 3 的更新从"全量 diff"降为"只比较动态部分",配合事件缓存(cacheHandlers)与静态字符串化,显著降低大规模列表、高更新频率页面的渲染成本——这是 Vue 3 相比 Vue 2"全量虚拟 DOM diff"性能优势的核心来源。

本题考察 Vue 3 编译时优化的整体图景:静态提升解决"对象重复创建",PatchFlag 解决"比较范围",Block Tree 解决"遍历路径"。回答时应把三者作为同一套"动静分离"策略的不同侧面阐述,并说明编译优化是"自动的",开发者主要收益在更新性能与内存占用,且不影响模板写法。

#
★★★

11.

本题考察 SFC 两种脚本块的作用域模型:实例作用域 vs 模块作用域。回答应围绕"宏为什么只能出现在 setup"(编译期展开到 setup 上下文)、"普通 script 的价值"(模块副作用、补充选项、具名导出)展开;能举出双块共存的实例(如 defineOptions + 模块级逻辑)即为掌握。

#
★★

12. Transition 与 TransitionGroup 在大型 Vue 项目的工程价值

Transition 与 TransitionGroup 在大型 Vue 项目中有哪些工程价值?使用时有哪些注意点?

  • Transition 单元素/组件的进出场动画,CSS 类名与 JS 钩子
  • TransitionGroup 列表增删移的过渡,key 与 move 类
  • 动画与懒加载、性能的平衡

Transition 为单个元素/组件的插入、更新、移除提供过渡:自动应用 v-enter-from/v-enter-active/v-enter-to 与 v-leave-* 系列类名(或通过 JS 钩子 beforeEnter/enter/leave 驱动动画库),并正确处理"离开动画完成后再移除 DOM"与过渡期间的事件。TransitionGroup 面向列表:为新增、移除、移动的项应用过渡,借助 v-move 类与 FLIP 技术实现位置平滑动画,key 是正确识别移动项的前提。大型项目的工程价值:统一的动画能力(配合 CSS 变量与设计系统 token)、避免手写进场/离场逻辑的时序错误、与 、路由视图、懒加载组件平滑集成。注意点:v-if/v-show 与过渡配合、初始渲染过渡 appear 的按需使用、大量列表项动画可能造成性能压力(需限制同时动画数量或降级)、以及 ensure leave 动画在组件卸载前的完整性。

本题考察内置动画组件的工程应用:先分清 Transition(单元素)与 TransitionGroup(列表)的能力边界,再谈注意点——key 的正确性、FLIP 移动动画、性能降级策略。回答时能结合大型项目实际(设计系统动画 token、列表拖拽排序、路由过渡)说明价值即为充分。

#
★★

13. defineProps 解构(Vue 3.5+)的响应式保留与 prop 更新追踪的工程价值

Vue 3.5+ 的 defineProps 解构如何保留响应性?它在 prop 更新追踪上的工程价值是什么?

  • 3.5 前解构 props 会丢失响应性,需 toRefs 或依赖 reactive 代理
  • 3.5 起编译器对解构赋值注入响应式转换
  • 解构变量与原始 props 的同步更新

在 Vue 3.5 之前,直接解构 defineProps 的结果会得到普通值,props 更新时解构变量不会同步变化,因此社区常用 toRefs(props) 或通过 props.xxx 访问。Vue 3.5 起编译器在编译

本题考察"编译期能力增强":3.5 的 props 解构是编译宏层面的转换而非运行时魔法,理解这一点能解释"为什么老版本失效、新版本有效"。回答时强调响应性保留、与 toRefs 的等价关系、以及单向数据流约束依然成立,即为完整。

#
★★

14. 的 include/exclude/max 在动态组件的缓存策略

的 include、exclude、max 属性如何控制缓存策略?在动态组件切换场景下如何使用?

  • include/exclude 按组件 name 匹配(字符串、正则、数组)
  • max 限制缓存实例数量,超出时淘汰最久未用实例
  • 动态组件 与 router-view 的缓存行为

通过 include/exclude 精确控制哪些组件被缓存:匹配基于组件的 name(或 SFC 中推断出的文件名),支持字符串、正则与数组;不匹配的组件切换时正常销毁重建。max 设定缓存上限,超出时按"最久未访问"(LRU 思想)淘汰最早缓存的实例,避免缓存无界增长导致内存膨胀。被缓存的组件在重新显示时触发 activated、隐藏时触发 deactivated 钩子(替代 mounted/unmounted 语义),用于按需恢复或挂起资源。工程价值:在 tabs 切换、多步骤表单、列表详情跳转等场景保留状态;用 include/max 控制缓存范围与内存预算;注意被缓存的组件仍占据内存,对大数据页面需结合 max 或按业务缓存策略评估。

本题考察 KeepAlive 的缓存治理能力:include/exclude 是"缓存哪些",max 是"缓存多少"。回答时点明 name 匹配机制、LRU 淘汰、activated/deactivated 钩子的语义替代,以及缓存内存成本的管理,体现对"状态保留 vs 资源占用"平衡的工程判断。

#
★★

15. defineExpose 与 useTemplateRef 跨组件实例的访问边界

defineExpose 与 useTemplateRef 如何实现跨组件实例的访问?访问边界在哪里?

  • defineExpose 显式暴露子组件方法/属性,默认全封闭
  • useTemplateRef 获取模板引用(DOM 或组件实例)
  • 父组件访问子组件公开 API 的类型支持

本题考察"跨组件访问的受控性":defineExpose 定义公共契约,useTemplateRef 获取实例,边界就是"只暴露该暴露的"。回答时强调封装原则(避免直接穿透内部状态)、获取时机(挂载后)、类型支持(3.3+ 泛型),并说明与 props/events 声明式通信的分工——命令式场景用暴露,数据流场景用 props/emit。

#
★★

16. Vue 3.5 组合式 API(Composition API)

Vue 3.5 对组合式 API 做了哪些增强?这些增强如何改善开发体验与工程实践?

  • 3.5 新增/改进的 API:useId、useTemplateRef、onWatcherCleanup、props 解构
  • 响应式重构带来的稳定性提升
  • 与 3.4 defineModel 等既有能力的协同

Vue 3.5 的组合式 API 增强集中在"易用性"与"确定性":useId() 解决 SSR 场景 ID 水合一致;useTemplateRef() 提供声明式、类型安全的模板引用;onWatcherCleanup() 让 watch 副作用清理成为一等公民;defineProps 解构(3.5 起)保留响应性;响应式内核重构(版本计数 + 双向链表)提升依赖追踪的精确性与性能。这些能力与 3.4 的 defineModel、useTemplateRef 前身、3.3 的泛型组件与 defineSlots 类型支持等构成递进体系,让组合式 API 在"状态管理、DOM 交互、SSR、类型安全"各维度都更完备。工程价值:组件库可提供更清晰的公共契约(useId + useTemplateRef + defineModel 组合),SSR 应用减少水合告警,复杂 watch 逻辑不再依赖手写清理变量,大型应用整体代码更可预测、更易测试。

本题是"版本特性综述"类问题,回答应覆盖 3.5 的增量而非罗列全部组合式 API:抓三个代表(useId、useTemplateRef、onWatcherCleanup + props 解构)讲清"解决什么问题",再谈内核重构与既有版本的衔接。能说明这些能力如何组合落地(表单组件、SSR、库开发)即为完整回答。

#
★★

17. Vue 3.5 的 provide/inject 在跨层级组件通信的现代实践

Vue 3.5 中 provide/inject 如何支持跨层级组件通信?现代实践中有哪些要点?

  • provide/inject 在 setup 中的使用与 Symbol 键
  • 默认值(inject 第二参数)与响应式数据注入
  • 与 reactive/ref 配合实现跨层级状态

provide/inject 允许祖先组件向任意深度后代注入数据,绕过逐层 props 透传:provide(key, value) 在祖先 setup 中提供,后代 inject(key, defaultValue) 消费,key 建议用 Symbol 或字符串常量避免冲突;注入值保持响应式(注入 ref/reactive 对象时,后代读取即响应)。Vue 3.5 中 inject 支持更完善的类型推导,且与组合式 API 无缝协作。现代实践要点:注入提供响应式对象而非一次性值;通过 provide 提供可注入的"服务"(如主题、表单上下文、命令总线);用 readonly 包裹防止后代随意修改共享状态;模块级 provide/inject 键独立维护避免命名碰撞。取舍:跨多层级、数量动态的依赖用 provide/inject;单层数据交换仍用 props/emit 保持显式;全局应用状态优先 Pinia,provide/inject 更适合"上下文类"数据。

本题考察依赖注入模式在 Vue 中的落地:回答核心是"响应式注入 + 键管理 + 只读约束 + 与状态管理分工"。能说明 provide/inject 适合"上下文"(表单容器、主题、弹层管理器)而非"全局状态"(那是 Pinia 的职责),并提及 Symbol 键与类型化 inject 即为充分。

#
★★

18. Vue 3.5 的 Suspense 在异步组件加载的边界

Vue 3.5 的 Suspense 组件在异步组件加载场景中的能力与边界是什么?如何使用?

  • Suspense 协调异步依赖(异步组件、async setup)的加载状态
  • 默认插槽与 fallback 插槽的切换时机
  • 嵌套 Suspense、事件与错误处理边界

Suspense 是一个试验性内置组件,用于协调其子树中的异步依赖:子树内的异步组件(defineAsyncComponent)或 async setup 完成前渲染 fallback 插槽(加载态),完成后切换到默认插槽内容;多个异步依赖时等待全部完成后一次切换,避免逐个闪现。边界与注意点:Suspense 只协调"其边界内"的异步依赖,被 KeepAlive 或 Transition 包裹的异步组件行为需配合使用;事件处理在 suspense 解析前可能延迟;Suspense 不提供内建错误处理,需结合 onErrorCaptured 或错误边界组件;嵌套 Suspense 各管各的边界;解析完成后异步组件内部错误仍走组件级错误处理。工程上 Suspense 与 defineAsyncComponent 配合实现组件级代码分割与骨架屏加载态,但官方仍标注实验性,生产环境需自行评估兼容与错误策略。

本题考察对"异步协调边界"的理解:Suspense 管的是"异步依赖集合的等待与切换",而非错误处理。回答时先讲机制(fallback 与 default 切换、多依赖聚合),再讲边界(错误处理缺位、嵌套与 KeepAlive/Transition 协作、实验性状态),体现"知道能力也知道限制"的工程认知。

#
★★

19. Vue 3.5 的 Fragment 支持多根节点组件的工程价值

Vue 3 的 Fragment 如何支持多根节点组件?它的工程价值与注意点是什么?

  • 组件模板允许多根节点,编译为 Fragment 类型 VNode
  • 属性继承(attr 自动继承)在多根节点下失效
  • 需要显式声明 inheritAttrs 或绑定 $attrs

Vue 3 中组件模板支持多个根节点,编译器将其编译为 Fragment(碎片)类型的 VNode,渲染时展开为多个真实 DOM 节点,无需额外包裹元素。相比 Vue 2 强制单根(常被迫嵌套无意义 div),Fragment 让布局更干净:Flex/Grid 直接子项、table 行分组等场景不再被包裹元素破坏;也避免多余的 DOM 层级带来的样式与性能开销。注意点:多根组件无法自动继承 fallthrough 属性(class、style、事件、自定义 attr),因为这些属性不知道该挂到哪个根上——需要手动通过 $attrs 显式绑定到具体节点,或在单个根上保留继承;组件间通信(如 v-model、事件)不受影响。工程上 Fragment 也提升了 v-for 多元素、条件渲染组合的灵活度,但设计组件 API 时需明确"多根 + 手动 $attrs"的约定。

本题考察 Fragment 的机制与副作用:价值在多根免包裹(布局语义、DOM 精简),代价是属性继承策略变化。回答时把"为什么 Vue 3 能多根(Fragment 编译产物)"与"多根后 fallthrough 行为的改变(需 $attrs 手动绑定)"两条线讲清,即覆盖要点。

#
★★

20. Vue 3.5 的 KeepAlive 在路由缓存与组件复用的工程取舍

Vue 3.5 的 KeepAlive 在路由缓存与组件复用场景下如何取舍?有哪些实践要点?

  • router-view 外包 KeepAlive 实现路由级缓存
  • include/exclude/max 与路由 meta 的结合
  • 缓存实例的生命周期钩子(activated/deactivated)

路由缓存的标准做法是 ,配合路由 meta 动态控制 include/exclude(如需要缓存的页面在 meta.cache 标记),实现"列表页保留滚动位置、详情页返回不重新请求"等体验。取舍要点:缓存以内存换体验——被缓存的路由组件保持实例与 DOM 状态,占内存;长列表、大图表页面应谨慎缓存或用 max 限制数量;多标签页场景用"tab key"作为缓存维度需自定义管理。实践要点:activated 中刷新过期数据(如重新拉取列表)、deactivated 中挂起轮询/监听;路由参数变化导致同一组件不同数据时,用 :key 区分缓存单元;明确"缓存的是组件实例,不是请求结果",数据新鲜度由业务决定。

本题考察 KeepAlive 与路由体系的集成决策:回答结构应为"怎么配(router-view + KeepAlive + meta)→ 怎么管(include/exclude/max + activated 刷新)→ 怎么权衡(内存成本、数据新鲜度、key 策略)"。能给出多标签页与参数化页面的处理方案即为完整。

#

21. Vue 3.5 的 Transition 与 TransitionGroup 在动画编排的工程实践

Vue 3.5 中 Transition 与 TransitionGroup 在动画编排上有哪些工程实践?如何组织复杂动画?

  • 进出场动画的类名钩子与 appear 初始动画
  • TransitionGroup 列表增删移的 FLIP 动画
  • 与 JS 动画库(GSAP)的钩子协作

动画编排的工程实践要点:单元素切换用 Transition,配合 v-enter/v-leave 系列类名或 beforeEnter/enter/leave JS 钩子驱动动画库(GSAP、anime),钩子中返回 Promise 可串行编排多个动画阶段;初始渲染动画用 appear 属性按需启用。列表场景用 TransitionGroup:新增/移除项进出场动画 + v-move 类实现位置 FLIP 动画,注意项必须提供稳定 key 且容器样式(如 display: flex 下需显式定位)保证 FLIP 生效。编排复杂动画时:拆分元素动画到独立 Transition/TransitionGroup 并控制时机(延迟、交错),用 CSS 变量与设计系统 token 统一动效参数,遵循系统 reduce-motion 偏好降级动画。性能上限制同时动画的节点数量、避免动画期间触发布局抖动(transform/opacity 优先),必要时关闭动画保底。

本题考察动画的工程组织能力:回答应包含"类名/JS 钩子两条驱动路径、FLIP 移动动画、动画编排与降级"四个层次。能说明 JS 钩子与 Promise 编排、prefers-reduced-motion 降级、transform 动画的性能原则,即达到工程实践深度。

#

22. Vue 3.5 的 defineOptions 在组件选项声明的现代实践

defineOptions 宏用于声明哪些组件选项?在

  • defineOptions 声明 name、inheritAttrs、customOptions 等编译期选项
  • 弥补

defineOptions 是

本题考察

#

23. Vue 3.5 的自定义指令(v-custom)在组件复用与原生 DOM 的取舍

Vue 3 的自定义指令(如 v-custom)在组件复用与原生 DOM 操作之间如何取舍?如何实现?

  • 自定义指令的钩子(mounted/updated/unmounted 等)与 binding 参数
  • 指令直接操作 DOM 的边界与封装
  • 与组件封装、composable 的取舍

自定义指令通过 app.directive('custom', { mounted(el, binding) {}, updated() {}, unmounted() {} }) 或 vCustom 局部注册实现,钩子中拿到绑定元素 el 与 binding(value、arg、modifiers、instance),直接操作原生 DOM。取舍:指令适合"以 DOM 为中心的声明式增强"——表单校验提示、权限(v-permission 控制移除/禁用)、懒加载(v-lazy 挂 IntersectionObserver)、滚动吸附、点击外部等;其代价是逻辑游离于组件响应式体系之外、难以测试与类型推导。当增强涉及业务状态、生命周期或需要复用逻辑时,优先用组件封装或 composable(更可测试、可组合);指令保留给"纯 DOM 行为 + 模板声明式用法"的场景,并注意卸载时清理监听器与观察器避免泄漏。

本题考察指令的定位判断:指令 = 模板层面的 DOM 行为声明。回答应给出实现模式(钩子 + binding + 卸载清理)与取舍原则(DOM 行为用指令、业务逻辑用组件/composable),并举例典型应用(权限、懒加载、表单校验),体现"不同抽象层级"的工程判断。

const vFocus = {
  mounted: (el) => el.focus(),
  unmounted: (el) => { /* 清理监听 */ }
}