前端数据对象命名:DTO、VO
前端数据对象命名:DTO、VO
花洒!!!
2026年8月24日
前端TypeScript架构分层
前端工程中的数据对象命名约定——DTO、Model、VO/ViewModel、FormModel 与 Store 模型,以及"按需拆"的实用原则。
前端数据对象命名:DTO、VO
后端有 PO/DO/VO 一套规范,前端也要照搬吗?答案是有对应物,但动机完全不同、松紧也不同。
前端为什么也要拆
- 后端拆对象一半是为了安全(防密码泄露),前端数据本来就在浏览器里,没这层压力。
- 前端拆对象的目的只剩两个:解耦(接口字段变化不波及组件)和类型安全(TypeScript 下类型即文档)。
- 拆也有成本:每层映射都是样板代码。所以前端的基调是按需拆,不是按规范拆。
常见对象
| 命名 | 对应后端 | 典型位置 | 用途 |
|---|---|---|---|
DTO(LoginRequest、UserDTO) | DTO | api 层 | 请求体 / 响应体类型,与接口字段基本 1:1 |
Model / Entity | DO | api 层 | 接口原始数据模型,本质是后端 DO/DTO 的 TS 版 |
VO / ViewModel(UserVM) | VO | 组件 / 页面 | 展示模型,组件真正消费的形状:拼接显示名、格式化日期、加计算属性 |
FormModel | DTO | 表单 | 与表单控件双向绑定的模型,提交时组装成请求 DTO |
| Store 模型 | 无 | Pinia / Redux | 全局状态里的数据形状,常直接复用 Model |
两点注意:
- 前端没有 PO/DO 那一档——除非用本地数据库 / ORM(Dexie、WatermelonDB)或做 DDD,才谈得上"持久化对象"。
VO和DTO同名多义最常踩坑:UserDTO可能是响应类型也可能是请求类型,靠命名区分(CreateUserRequest/UserListItemDTO)。
转换链路
典型链路:api 层 Model ⇄ 组件展示模型(VO/ViewModel)⇄ 表单(FormModel)。
// api 层:接口原始形状
interface UserDTO {
id: number
lastLoginAt: string
roleIds: number[]
}
// 展示层:组件要的形状
interface UserVM {
displayName: string // 拼接后的显示名
lastLoginText: string // 格式化后的时间
}
转换工具:简单场景手写映射;接口多时可在 api 响应拦截器统一转换,或用函数式映射(toVM(user: UserDTO): UserVM)集中管理。
按需拆的原则
什么时候拆:接口字段与页面展示差异大、多个页面共用同一接口但展示不同、提交结构与展示结构不同。
什么时候不拆:展示与接口一致、小项目、原型——interface 一把梭完全合理,不丢人。
常见误区
- 为拆而拆:每个接口配三个类型文件,映射代码比业务还多。
- 一把梭到底:接口字段改了,全项目类型报错或悄悄漏改,页面样式悄悄崩。
any满天飞:类型都写成any,等于没拆,还丢了 TS 的全部价值。
小结
主线一句话:前端类型跟着"用途"走——传输用 DTO、展示用 VO、表单用 FormModel,够用就停。别拿后端那套 PO/DO 硬套前端。
适用:中大型 TypeScript 项目、接口与展示差异明显的场景;不适用:小工具、原型、展示与接口一致的小页面。