当你用 TypeScript 构建一个 GraphQL 客户端时,直接从服务器模式生成类型很诱人。毕竟,模式不就是你 API 的唯一事实来源吗?
不是这样的。虽然数据模式定义了所有可能返回的字段,但它并不保证在任何特定情况下实际返回哪些字段。你的 React 组件、Vue 视图或 Svelte 页面只会接收到它们实际查询到的字段,而不是整个模式中的所有字段。这就是为什么你应该根据你的查询和片段来生成客户端 TypeScript 类型,而不是从抽象模式生成。
这么想:架构就像一份完整的餐厅菜单,但你的查询则是你实际点的菜品。厨房只会端出你所点的菜品,所以你的“盘子”(UI 中的数据)需要,一个反映实际呈现内容的类型,而不是整份菜单上的所有内容。
示例:架构与查询之间的脱节架构就是:
type 用户 {
id: ID!
name: String!
email: String!
profilePicture: String
}
你的组件用到的查询是:
query 查询用户 {
用户(id: "123") {
id
名字
}
}
如果你依赖于由模式生成的 User
类型,你可能会以为 email
和 profilePicture
总是存在的——却发现运行时它们缺失了,因为查询没有请求这些属性。通过直接从 GetUser
查询生成类型,你的 TypeScript 类型将仅包含 id
和 name
,完美地匹配了组件实际接收到的数据。
有什么问题?
这个模式定义了所有可能的字段,其中你的组件从未用到的字段有很多。
为什么这很重要
基于模式定义类型可能导致运行时错误。你的代码可能假设例如 email
这样的字段总是存在的,即使没有请求这些字段。
换句话说
要求少一点,就别装作占了便宜。
问题是什么?
基于模式的类型定义无法准确反映运行时实际存在的内容。你需要为可能不存在的字段编写额外的检查并使用可选链。
为什么这很重要
查询驱动的类型匹配服务器实际返回的数据,这样你的组件代码就可以信任类型系统。更少的空值检测,更少的意外。
换句话说
你的类型应该反映你实际得到的,而不仅仅是理论上可能得到的。
有什么问题?
当你添加新功能时,你经常会修改查询——增删字段或调整参数。如果类型是从模式衍生的,你每次都需要手动同步这些类型。
为什么这很重要呢
通过使用例如 @graphql-codegen/cli
这样的工具从操作中生成类型,每当您的查询有变动时,类型会保持完全同步,这让类型无需手动更新就能保持同步。
换句话说
你的类型永远不会误导你关于实际返回的内容。更新查询,重新生成,就完成了任务。
问题在哪里?
模式级别的类型会暴露所有可能的字段,使你的IDE的建议变得混乱。你可能会浪费时间搜索那些查询根本不会获取的无关字段。
为什么这很重要呢?
查询会提供你请求的确切字段。你的编辑器的自动完成建议和内嵌帮助文档会更精确和有用。
这样你的编辑器会更准确、更实用地为你服务。
换句话说
你只能看到你盘子里的东西,看不到整个菜单。
出了什么问题?
当类型与实际返回的数据不符时,新成员加入团队时会遇到更多困难。他们可能编写代码时假设某些字段存在,结果却遇到运行错误。
为什么这很重要
当你的类型准确反映实际查询结果时,团队成员都相信所见即所得。这减少了前后端团队之间的沟通往来,从而加快了开发进程。
换句话说,你的团队可以
自信地写代码,而无需猜测或反复查阅API文档来确认返回的内容。
- 代码生成
将@graphql-codegen/cli
集成到您的构建过程中,直接从查询和片段生成 TypeScript 类型定义。这样可以确保您的类型始终是最新的,保持代码的一致性和可靠性。 - Apollo 和 The Guild 的推荐
Apollo 文档 和 GraphQL Code Generator 的官方文档 推荐从操作生成类型。这是一项公认的行业标准,而非个人喜好。
你的 GraphQL 架构定义了可以返回的数据,而你的查询则定义了实际返回的数据。通过从查询和片段生成 TypeScript 类型定义,你可以确保你的类型系统与实际需求完全一致。这种方法不仅可以节省时间,减少困惑,还能让你的团队更有信心地交付功能特性。
接下来该做什么呢
- 将
@graphql-codegen/cli
集成到您的工作流程中吧。 - 从您的查询和片段生成类型。
- 享受更干净的代码,更少的意外情况,以及更快乐的开发团队。
保持你的类型一致,让查询来引导方向吧!
阅读《The GraphQL Guide》了解更多关于GraphQL的最佳实践。
共同学习,写下你的评论
评论加载中...
作者其他优质文章