Skip to content

Vue 项目场景题

场景 1:后台表单越来越复杂,你会怎么设计?

面试官可能这样问:

一个订单配置页面有几十个字段,字段之间还有联动、校验和动态显示。你用 Vue3 怎么设计?

回答思路:

  • 表单状态可以按业务模块拆分,不要所有字段堆在一个大对象里。
  • 复杂联动逻辑放到 computed 或组合式函数中。
  • 校验规则集中管理,避免散落在模板里。
  • 下拉选项、远程数据、默认值初始化要有 loading 和错误状态。
  • 如果表单会复用,可以抽出 useOrderForm 或配置化表单。

项目化解释:

真实项目里,这种页面最怕“字段一多,谁也不敢改”。我会先把表单分成基础信息、价格规则、库存规则、权限范围几个区块。每个区块有自己的状态和校验,最终提交前再组装成接口需要的数据。

如果某个字段控制另一个字段是否显示,我不会在模板里写一堆复杂表达式,而是写成:

ts
const showDiscountConfig = computed(() => form.priceType === 'discount')

这样后面产品改规则时,只需要改一个地方。

追问点:

表单数据如何回显?

接口数据先经过 adapter 转成前端表单结构,再统一 reset 或赋值。异步下拉选项要先加载选项,再回显选中值,避免出现只有 ID 没有 label 的情况。

如何处理接口字段和前端字段不一致?

用数据转换层隔离差异,例如 toFormModeltoSubmitPayload。组件内部使用适合页面表达的字段,提交时再转换成后端需要的字段。

如何防止重复提交?

前端在提交中禁用按钮并加 loading,后端用幂等 key 或唯一约束兜底。关键业务不能只靠前端防重复。

如何做离开页面前的未保存提醒?

维护一个 dirty 状态,表单初始值和当前值不一致时标记为已修改。路由离开、关闭页面或切换标签时提示用户保存或放弃。

场景 2:Vue 页面首屏慢,你怎么排查?

面试官可能这样问:

一个 Vue3 页面打开很慢,你会从哪些方向排查?

回答思路:

  • 先用浏览器性能面板判断是网络慢、JS 执行慢还是渲染慢。
  • 看首屏 bundle 是否过大,是否需要路由懒加载。
  • 看接口是否串行请求,能否并行或拆分。
  • 看组件是否渲染过多,是否有大列表或复杂图表。
  • 看图片、字体、第三方库是否阻塞首屏。

项目化解释:

我会先看 Network,如果 JS 包很大,就做按路由拆包,把图表、编辑器、富文本这类重组件改成异步组件。如果接口耗时长,我会区分首屏必须数据和非首屏数据,先显示主内容,再懒加载次要模块。

如果列表几千条一次渲染,我会改成分页或虚拟滚动。优化后会对比 LCP、接口耗时和用户可操作时间,而不是只凭感觉说变快了。

追问点:

路由懒加载怎么写?

在路由配置里使用动态导入:component: () => import('@/views/Dashboard.vue')。这样页面对应的代码会按需加载,减少首屏包体积。

defineAsyncComponent 适合什么场景?

适合异步加载重组件,例如图表、富文本、地图、复杂弹窗。它可以配合 loading、error 和 timeout 状态,让非首屏组件延后加载。

如何分析打包体积?

使用构建分析工具查看 chunk 组成,重点看第三方库、重复依赖、图表库、编辑器、图片资源。优化方向包括按需引入、拆包、懒加载和替换更轻依赖。

如何避免优化后引入白屏?

保留错误边界、加载态和降级内容;懒加载组件失败时要有 fallback。上线前验证深层路由刷新、弱网、缓存和资源 404 场景。

场景 3:Pinia 全局状态越来越乱怎么办?

面试官可能这样问:

项目里大家什么状态都往 Pinia 里放,store 越来越大,你怎么治理?

回答思路:

  • 先区分局部状态、页面状态和全局状态。
  • 只把跨页面、跨组件、需要持久化或共享的状态放 Pinia。
  • 按业务域拆 store,比如 user、permission、cart、settings。
  • action 命名要清晰,避免组件到处直接改复杂状态。
  • 对持久化状态做白名单,不要把所有数据都存本地。

项目化解释:

真实项目里,我会先整理 store 使用情况。比如弹窗开关、表格筛选条件如果只在一个页面用,就回收到页面组件里。用户信息、权限菜单、主题配置这种才放全局。

如果某个 store 超过几百行,我会按领域拆分,而不是继续加字段。这样排查 bug 时能快速定位是哪块业务状态出了问题。

追问点:

storeToRefs 为什么有用?

它可以在解构 store 时保留响应式。直接解构普通属性可能丢失响应式,而 storeToRefs 会把状态转成 ref。

Pinia 如何持久化?

可以用插件或手动监听,把需要持久化的状态写入 localStorage、sessionStorage 或 Cookie。建议使用白名单,不要把所有 store 都持久化。

如何处理退出登录清空状态?

退出登录时清空 token、用户信息、权限菜单和业务缓存,并重置相关 store。必要时调用每个 store 的 $reset,再跳转登录页。

如何避免刷新页面权限路由丢失?

刷新后重新读取 token,请求用户权限,再动态 addRoute。路由恢复完成前可以显示加载页,避免直接进入 404。

场景 4:组件通信很乱,你怎么重构?

面试官可能这样问:

一个页面里父子、兄弟、跨层级组件传值很多,事件到处 emit,你会怎么优化?

回答思路:

  • 简单父子通信继续用 props / emit。
  • 多层级但稳定的上下文用 provide / inject。
  • 跨页面共享状态用 Pinia。
  • 复杂页面可以提升状态到页面容器组件。
  • 通用组件保持无业务依赖,通过 props 和事件暴露能力。

项目化解释:

我会先画出组件关系,找出到底是谁拥有这份状态。如果状态属于页面,就放页面容器里,子组件只负责展示和触发事件。如果状态属于全局业务,比如当前用户权限,就放 Pinia。

重构目标不是消灭所有 props,而是让数据流可追踪:谁拥有状态、谁修改状态、谁消费状态都清楚。

追问点:

provide / inject 适合替代 Pinia 吗?

不完全适合。provide/inject 适合组件树内部共享上下文,例如表单、主题、布局;Pinia 适合跨页面、跨模块的全局业务状态。

defineExpose 什么时候用?

当父组件需要主动调用子组件方法时使用,例如表单校验、重置、打开弹窗。不要滥用,否则会让组件耦合变强。

如何设计一个通用表格组件?

把列配置、数据源、分页、排序、筛选、加载态、空态和操作列抽象出来。通用组件负责结构和交互,业务渲染通过 slot 扩展。

如何避免组件过度封装?

先看是否有稳定复用场景,再抽象。只复用一次或差异很大的功能不急着封装,避免为了通用而暴露大量复杂配置。

Built with VitePress and GitHub Pages.