问题清单与项目表达
Vue 问题清单与项目表达
主线学完的收官篇。这份清单不是拿来背的, 是拿来自检的: 每个问题我都要求自己能连着”机制 + 自己项目里的代码证据 + 踩过的坑”一起讲出来, 讲不出来就回对应篇目补课。被问到项目的场合 (答辩、技术交流、和后端同事对齐), 这套东西就是底气。
1. 核心机制
ref、reactive、computed、watch 怎么选?
先问”这是数据、推导值, 还是外部动作”, 别问”哪个 API 高级”。会变的数据用 ref (对象整体替换也用它), 稳定字段的对象可用 reactive; 能从现有状态算出来的用 computed; 要发请求、写缓存这类副作用才用 watch。
出处: 01 篇。
Vue 3 为什么能检测到新增属性, Vue 2 不能?
Vue 2 用 Object.defineProperty 给已存在的属性一个个装 getter/setter, 后加的属性没装, 所以要 Vue.set 补。
Vue 3 用 Proxy 代理整个对象的操作, 新增、删除、数组下标全拦得到。但 Proxy 也不是魔法: 普通变量重新赋值它管不着 (这正是 ref 要包一层的原因)。
computed 为什么通常比 watch 合适?
筛选结果、总数、按钮禁用态都是”由状态决定的值”, computed 声明式加缓存一步到位。用 watch 手动同步一份结果, 等于自己维护第二份状态, 时序、漏更新的坑全是自己挖的。我重构自己代码时的第一件事往往就是把 watch 改 computed。
nextTick 解决什么?
Vue 攒一批状态变更一起刷 DOM, 所以改完状态的下一行 DOM 还是旧的。nextTick 等本轮更新刷完, 典型场景是聚焦刚显示出来的输入框。它治不了数据流设计错误, 到处 nextTick 是坏味道。
key 为什么不能用数组下标?
diff 靠 key 认”哪个节点是同一个”。中间删一项, 后面所有下标全变, 旧组件实例被张冠李戴地复用, 症状是输入框内容串行。稳定业务 ID 才能表达”这还是那条记录”。这个坑我在 01 篇动手验收里亲手造过一次。
2. 组件与数据流
props 为什么不能直接改?
数据的主人是父组件。子组件绕过父组件直接改, 修改路径就藏起来了, 而且父组件一重渲染可能把改动冲掉。正确姿势是 emit 意图, 主人自己改, 单向数据流 (02 篇)。
组件 v-model 的底层是什么?
一个 modelValue prop 加一个 update:modelValue 事件的语法糖; 具名的 v-model:visible 就是 visible + update:visible。
懂了底层才能封装弹窗、分页这类双向组件, 也才看得懂 Vue 2 的 value/input 老约定。
slot 和 prop 的边界?
prop 传数据和配置, slot 让调用方控制一段结构。判断信号: 组件冒出一堆布尔 prop 来拼装结构时, 基本就该改 slot 了。
什么时候用 provide/inject?
祖先提供、一片深层后代消费的局部依赖 (表单上下文、主题)。它对使用位置有隐性约束, 拿来当全局 Store 用就是滥用, 跨页面的东西归 Pinia。
3. Router 与状态
params、query、Store 怎么分工?
资源身份进 params (/tasks/42), 刷新分享要保留的筛选进 query, 跨页面又不适合进 URL 的 (用户、权限) 进 Store, 单组件临时交互放本地。检验方法很简单: 刷新一下、把链接发给别人, 该在的状态还在不在。
路由守卫为什么会死循环?
八成是”未登录踢去登录页, 登录页自己也被拦”。显式放行公共页, 守卫统一用”返回导航目标”的写法, redirect 参数只允许站内路径 (03 篇)。
前端隐藏菜单能保护接口吗?
不能, 前端一切权限控制都只是体验层, F12 面前没有秘密。后端必须独立鉴权身份、角色、资源归属。这句话在 03 和 05 篇各强调过一次, 因为它值得。
为什么别把所有数据塞进 Pinia?
全局状态扩大依赖图, 调试和持久化成本都涨; 局部状态进了 Store 组件就不可复用了; URL 状态进了 Store 刷新就丢。先按生命周期和共享范围找归属 (04 篇)。
4. 异步与异常
怎么防止旧请求覆盖新结果?
首选取消: AbortController 配 watch 的 onCleanup。取消不了就用递增序号, 只有最新序号有权写结果。只靠一个 loading 布尔值扛并发, 早晚出事。两种写法分别在 01 篇和 04 篇。
常见 HTTP 状态码, 页面各做什么?
| 状态 | 含义 | 页面动作 |
|---|---|---|
| 401 | 未认证/token 过期 | 清会话回登录 (全局只跳一次) |
| 403 | 已认证但无权限 | 展示无权限, 别再跳登录 |
| 404 | 资源不存在 | 空状态或”不存在”页 |
| 409 | 版本/业务冲突 | 提示刷新或重新确认 |
| 422 | 字段校验失败 | 错误映射到表单项 |
| 500 | 服务端异常 | 通用错误 + 重试 + requestId |
写过 Spring 接口的话这张表两边都熟, 关键是前端动作别乱: 403 跳登录、422 只弹个”失败”, 都是把用户往沟里带。
为什么不能在 finally 里无条件关 Loading?
并发时先结束的请求会把还在跑的请求的 Loading 关掉。计数器或最新序号判断, 05 篇 useRequest 里就是这么写的。
5. 性能与稳定性
大列表怎么优化?
先测量再动手: 是响应体大、DOM 多、每行计算贵还是重复渲染? 对症下药依次是后端分页、虚拟列表、稳定 key、computed 缓存、shallowRef。没测量就上 v-memo 属于玄学优化。系统方法在性能优化篇。
内存泄漏是怎么来的?
组件销毁了, 但 window 监听、定时器、WebSocket、图表实例还攥着回调。规矩: 谁注册谁清理, 全部收口到 onBeforeUnmount; KeepAlive 场景还要处理 onActivated/onDeactivated。
首屏包大从哪查起?
构建分析看 chunk 构成, 惯犯是富文本编辑器、图表库、日期库全量引入。路由懒加载 + 按需导入 + 换轻量库, 比盲目拆小组件有效得多。
6. Vue 2 存量项目检查单
接手老项目前过一遍: data 必须是函数; 新增响应式字段要 Vue.set; 卸载钩子叫 beforeDestroy/destroyed; v-model 默认对应 value/input; 过滤器和事件总线要知道现代替代方案; Vuex 的 mutation 同步约束不能破坏。
7. 项目表达模板
把功能讲清楚的套路, 我按这个顺序组织一段话:
- 背景: 谁在什么场景遇到什么问题
- 约束: 数据量 / 权限 / 接口延迟 / 交付时间
- 方案: 组件边界、状态归属、路由、请求层怎么组合
- 关键实现: 一段能体现取舍的代码
- 故障: 最初哪里错了, 怎么复现定位修复
- 结果: 延迟 / 错误率 / 体积 / 维护成本改善了多少
- 复盘: 重来一次会提前设计哪些边界
拿请求竞态这个我真踩过的坑套一遍:
- 背景: 用户快速切换筛选, 接口延迟高, 旧结果覆盖新结果。
- 约束: 后端不支持取消请求, 要兼容慢网。
- 方案: 筛选条件放 URL query; watch 触发请求; 每次请求分配递增序号, 只有最新序号能写入列表。
- 故障: 最初只用 loading 布尔值, 复现方法是给旧请求人为加 2 秒延迟。
- 结果: 乱序响应不再污染页面, 链接可分享可复现。
- 复盘: 接口若支持 AbortSignal, 应该直接取消无意义的旧请求。
比起罗列”用了 Vue3 + Pinia + Element Plus”, 这种带故障和取舍的叙述有说服力得多。
8. 自我检查清单
- 能从空目录搭出 Vue 3 + Router + Pinia + TS 项目并解释每一步。
- 能封装带 v-model、校验、Loading、错误回写的表单组件。
- 能指出自己项目里每个状态的唯一所有者。
- 能亲手复现并修复: key 错误、请求乱序、路由死循环、内存泄漏。
- 谈性能时能给出测量证据, 而口头禅”我用了缓存”给不出。
- 能在 Vuex 存量代码里安全改一个 module。
9. 最终交付
做完自己的后台项目后, 留下这几样材料: 状态与路由关系图、API/DTO 与错误码说明、三个关键组件的设计记录、一条含失败重试的端到端流程、一次构建体积和慢请求的测量记录、一页”问题 → 定位 → 修复 → 复盘”的故障档案。这些东西才能证明我从”会写示例”走到了”能维护系统”。