108 lines
6.6 KiB
Markdown
108 lines
6.6 KiB
Markdown
# Vue 架构与职责边界
|
||
|
||
## 目录
|
||
|
||
- 结构原则
|
||
- 组件边界
|
||
- Composable
|
||
- Pinia
|
||
- Vue Router
|
||
- API 与数据边界
|
||
- 表单
|
||
- SSR 与 Hydration
|
||
- 依赖和公共 API
|
||
- 官方依据
|
||
|
||
## 结构原则
|
||
|
||
- 先遵循现有目录结构;不要在单个需求中引入第二套架构。
|
||
- 新建中大型项目或独立业务域时,优先按 feature/domain 聚合页面、组件、composable、store、服务和类型;将真正跨域的稳定能力放入 `shared`。
|
||
- 避免把 `components/`、`utils/`、`types/` 变成无边界仓库。目录名需表达所属业务或技术职责。
|
||
- 只允许单向依赖:应用层可依赖共享层,共享层不得反向依赖具体页面或业务域。
|
||
- 在浏览器、服务端、存储、网络和第三方 SDK 边界建立适配层,避免组件直接承载协议细节。
|
||
|
||
## 组件边界
|
||
|
||
- 让页面组件负责路由级编排,让业务组件负责明确用例,让基础组件负责可复用交互和展示。
|
||
- 组件同时承担数据请求、复杂业务计算、多块无关 UI 和跨域副作用时,按职责拆分。
|
||
- 不按行数机械拆分;仅在职责、复用、测试隔离或性能边界更清晰时提取。
|
||
- 保持公共组件 API 小而稳定。避免大量布尔 props 组合出互斥模式;使用判别联合、variant 或具名 slots。
|
||
- 不让父组件通过 DOM 查询或 template ref 修改子组件内部状态。
|
||
- 向下传递少量明确数据;深层跨树依赖使用 provide/inject 时定义 typed injection key,并提供缺失依赖处理。
|
||
- 设计组件时覆盖默认、loading、empty、error、disabled 和只读等适用状态。
|
||
|
||
## Composable
|
||
|
||
- composable 封装可复用的有状态逻辑、生命周期或外部系统连接,不把纯函数包装成 composable。
|
||
- 接收明确参数或响应式输入;不要隐式读取任意全局单例,除非该依赖就是契约的一部分。
|
||
- 返回稳定、最小的公开 API。将 state 与操作区分清楚,必要时返回 readonly state。
|
||
- 由创建资源的一方负责释放资源;在内部注册 cleanup,或向调用方暴露明确 `stop`/`dispose`。
|
||
- 避免一个 composable 同时处理请求、表单、路由、通知和持久化;按用例编排更小能力。
|
||
- 对有宿主组件依赖的 composable 标明生命周期约束;纯响应式 composable 应可直接单测。
|
||
|
||
## Pinia
|
||
|
||
- 仅将跨组件、跨页面、需持久化或需 DevTools 跟踪的状态放入 store。
|
||
- 每个 store 表达一个稳定业务域,使用唯一 id,并在独立文件定义。
|
||
- state 保持最小;派生值使用 getters/computed;状态改变和业务动作通过 actions 或明确方法完成。
|
||
- 不在组件外随意解构 store;在组件中用 `storeToRefs` 解构响应式 state/getters,actions 可直接解构。
|
||
- setup store 必须返回所有应被 Pinia 管理的 state;不要依赖“私有 state”技巧破坏 SSR、DevTools 或插件行为。
|
||
- 不在 store 中保存 Router/Route、DOM 元素或不可序列化第三方实例;这些依赖留在调用层或适配层。
|
||
- 持久化仅保存必要字段,并处理版本迁移、过期、隐私与反序列化失败。
|
||
- 测试 store 的公开 action、getter 和状态转移,不依赖内部实现顺序。
|
||
|
||
## Vue Router
|
||
|
||
- 页面级组件使用动态 import 做路由懒加载;不要把异步组件与路由懒加载混为一谈。
|
||
- 优先命名路由,集中定义 path、name 和 meta 类型。
|
||
- 将认证、权限和通用重定向放在合适的全局或路由级 guard;不要把所有页面业务塞入全局 guard。
|
||
- guard 返回导航结果或抛出可处理错误;避免多次调用导航、隐式 fall-through 和未处理竞态。
|
||
- 客户端路由权限只控制体验,不构成安全边界;后端必须独立授权。
|
||
- 路由参数和 query 都是不可信字符串输入,使用前解析、校验并处理缺失或重复值。
|
||
- 编程式导航需要时等待结果并处理 navigation failure。
|
||
|
||
## API 与数据边界
|
||
|
||
- 将 HTTP 客户端配置、认证头、错误归一化和序列化放入 API 基础层。
|
||
- 按业务域定义 service/repository 函数,组件不直接拼接重复 URL 或处理底层响应结构。
|
||
- 为请求参数和响应 DTO 提供类型;对不可信响应在运行时校验关键字段。
|
||
- 在边界将 DTO 映射为前端领域模型,避免蛇形命名、nullable 约定和后端枚举扩散到整个 UI。
|
||
- 支持请求取消或过期响应丢弃;切页、快速搜索和重复提交必须处理竞态。
|
||
- 对 mutation 提供重复提交保护、幂等策略或清晰恢复方式。
|
||
- 不在客户端持有服务端密钥。`VITE_*` 内容会进入客户端 bundle,只能存放公开配置。
|
||
- 将用户可见错误转换为可行动信息,同时保留脱敏的诊断上下文。
|
||
|
||
## 表单
|
||
|
||
- 分离原始 DTO、可编辑表单值、验证结果和提交 payload。
|
||
- 明确字段 touched、dirty、validating、disabled 和 submitting 状态。
|
||
- 客户端验证用于即时反馈,服务端仍是最终业务校验来源。
|
||
- 服务端字段错误映射回对应控件,并提供表单级错误摘要或焦点移动。
|
||
- 避免 watcher 组成不可追踪的双向同步链;建立单一数据源和显式 reset/submit 流程。
|
||
- 离开页面可能丢失编辑内容时,按需求提供确认或草稿策略。
|
||
|
||
## SSR 与 Hydration
|
||
|
||
- 先识别项目是否为 Nuxt、SSR、SSG 或纯 SPA,再选择数据获取和生命周期位置。
|
||
- 不在服务端共享请求级可变单例,避免用户间状态泄漏。
|
||
- 将浏览器 API 放在客户端生命周期或带环境判断的适配层。
|
||
- 保证服务端与首次客户端渲染结构一致;时间、随机数、locale 和设备判断需避免 hydration mismatch。
|
||
- setup store、持久化插件和请求缓存必须符合项目 SSR 约定。
|
||
|
||
## 依赖和公共 API
|
||
|
||
- 新增依赖前证明现有能力不足,并评估体积、维护状态、类型、许可证、SSR 和 tree-shaking。
|
||
- 不因单个小功能引入大型工具库。
|
||
- 公共组件、composable、store 和类型一旦被多个模块使用,即视为契约;修改时检查全部调用方并补充回归测试。
|
||
- 删除或重命名公共导出前,先迁移调用方并确认兼容策略。
|
||
|
||
## 官方依据
|
||
|
||
- [Vue Composables](https://vuejs.org/guide/reusability/composables.html)
|
||
- [Vue 状态管理](https://vuejs.org/guide/scaling-up/state-management.html)
|
||
- [Pinia:定义 Store](https://pinia.vuejs.org/core-concepts/)
|
||
- [Vue Router:懒加载路由](https://router.vuejs.org/guide/advanced/lazy-loading.html)
|
||
- [Vue Router:导航守卫](https://router.vuejs.org/guide/advanced/navigation-guards.html)
|
||
- [Vite 环境变量与模式](https://vite.dev/guide/env-and-mode)
|
||
|