Vue 项目场景题
场景 1:后台表单越来越复杂,你会怎么设计?
面试官可能这样问:
一个订单配置页面有几十个字段,字段之间还有联动、校验和动态显示。你用 Vue3 怎么设计?
回答思路:
- 表单状态可以按业务模块拆分,不要所有字段堆在一个大对象里。
- 复杂联动逻辑放到
computed或组合式函数中。 - 校验规则集中管理,避免散落在模板里。
- 下拉选项、远程数据、默认值初始化要有 loading 和错误状态。
- 如果表单会复用,可以抽出
useOrderForm或配置化表单。
项目化解释:
真实项目里,这种页面最怕“字段一多,谁也不敢改”。我会先把表单分成基础信息、价格规则、库存规则、权限范围几个区块。每个区块有自己的状态和校验,最终提交前再组装成接口需要的数据。
如果某个字段控制另一个字段是否显示,我不会在模板里写一堆复杂表达式,而是写成:
const showDiscountConfig = computed(() => form.priceType === 'discount')这样后面产品改规则时,只需要改一个地方。
追问点:
表单数据如何回显?
接口数据先经过 adapter 转成前端表单结构,再统一 reset 或赋值。异步下拉选项要先加载选项,再回显选中值,避免出现只有 ID 没有 label 的情况。
如何处理接口字段和前端字段不一致?
用数据转换层隔离差异,例如 toFormModel 和 toSubmitPayload。组件内部使用适合页面表达的字段,提交时再转换成后端需要的字段。
如何防止重复提交?
前端在提交中禁用按钮并加 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 扩展。
如何避免组件过度封装?
先看是否有稳定复用场景,再抽象。只复用一次或差异很大的功能不急着封装,避免为了通用而暴露大量复杂配置。