技术文章/工程化

让类型自己说话:我如何组织一个中等规模项目的类型层

类型写到一定规模之后,问题就不再是"怎么写类型",而是"类型放在哪里、由谁负责"。

目录:三层就够

text
src/
├─ types/
│  ├─ api.ts        # 接口出入参,与后端契约一一对应
│  ├─ model.ts      # 领域模型,页面之间共享的那一份
│  └─ ui.ts         # 纯展示态类型(Tab 枚举、表单状态)

绝大多数项目的类型混乱,根源是把接口返回体直接当成领域模型用。后端字段一改,半个页面的类型全红;更糟的是,前端会不知不觉依赖上后端那些本不该暴露的字段。

边界:在数据入口处收口

只在两个地方做类型转换:请求封装层、以及跨页面共享的状态层。

ts
export async function fetchPost(slug: string): Promise<Post> {
  const raw = await http.get<ApiPost>(`/posts/${slug}`)
  return toPost(raw)          // ← 唯一的转换点
}

function toPost(raw: ApiPost): Post {
  return {
    id: raw.id,
    title: raw.title,
    tags: raw.tags.map((t) => t.name),
    publishedAt: new Date(raw.published_at),
  }
}

好处是后端改字段时,你只需要改 toPost 一个函数,而不是满仓库搜索。

什么时候该放弃类型体操

我曾经写过一个能自动推导嵌套可选字段类型的小工具,用了三层条件类型,注释比实现长。半年后我自己都没看懂。

判断标准很简单:如果这段类型代码需要一个专门的注释来说明它在干什么,那它多半不该存在。

as 或者手写一个显式接口,都比让人看不懂的推导更好。

最后一点

strict,然后不要any 去关掉它的报错。真要临时放行,用 unknown + 一次显式的类型守卫,至少让下一个读到这段代码的人知道这里有风险。

← 上一篇关于"做完"和"做好"之间那条线

评论

还没有评论,坐个沙发?