4、GraphQL 类型系统:标量类型、对象类型、枚举类型、列表类型、非空类型
类型系统,说白了就是 GraphQL 的骨架。你想想看,如果没有类型约束,前端随便传个字段,后端随便返回个数据,那跟 REST 接口的「文档与实现不符」有什么区别?GraphQL 之所以能成为强类型查询语言,靠的就是这套类型系统。
我个人习惯,在设计 API 之前,先把类型定义写好。就像盖房子先画图纸一样,类型定义清楚了,后面的 resolver 写起来就顺风顺水。今天咱们就把这五种核心类型掰开揉碎讲清楚。
标量类型:最基础的数据单元
标量类型,就是 GraphQL 里的「原子」。它不能再往下拆分了。就像你写代码时的 String、Int,是最小的数据单位。
GraphQL 默认提供了五个标量类型:
| 类型 | 说明 | 示例 |
|---|---|---|
Int |
32 位有符号整数 | 42 |
Float |
双精度浮点数 | 3.14 |
String |
UTF-8 字符串 | "Hello" |
Boolean |
布尔值 | true |
ID |
唯一标识符,序列化时当作字符串 | "abc123" |
这里有个坑,我刚开始用的时候踩过。ID 类型虽然序列化后是字符串,但它和 String 有本质区别。ID 表示「唯一标识」,不应该被用于展示或计算。我曾经在项目里把 ID 当 String 用,结果前端做排序时出了问题——因为 ID 的语义是「标识」而不是「文本」。
核心要点:标量类型是叶子节点,查询到标量类型就到底了,不能再往下查子字段。
对象类型:组合数据的核心
对象类型,就是你把多个标量类型组合起来。比如一个「用户」有名字、年龄、邮箱,这些字段组合在一起,就是一个对象类型。
定义方式很简单:
type User {
id: ID!
name: String!
age: Int
email: String
}
看到那个感叹号了吗?那是非空标记,咱们后面细说。先看对象类型本身——它就像 TypeScript 里的 interface,定义了数据的形状。
我在项目中遇到过一个问题:对象类型可以嵌套。比如 User 里有个 posts 字段,类型是 [Post],Post 又引用了 User。这就形成了循环引用。GraphQL 完全支持这种模式,但要注意,你的 resolver 里要处理好数据加载,别搞出死循环。
小技巧:对象类型的字段名建议用驼峰命名,和 JavaScript 风格保持一致。我见过用下划线的项目,前端每次都要做字段映射,很麻烦。
枚举类型:限定可选值
枚举类型,说白了就是「只能从这几个值里选一个」。比如用户的角色:管理员、普通用户、访客。用枚举来定义,比用字符串更安全。
enum Role {
ADMIN
USER
GUEST
}
type User {
id: ID!
name: String!
role: Role!
}
为什么推荐用枚举?我举个例子。以前有个项目,用户角色字段用 String 存,结果前端传了个 "admin"(小写),后端存的是 "ADMIN"(大写),两边对不上,查了半天 bug。用枚举之后,GraphQL 会在查询阶段就校验值是否合法,不合法直接报错,根本不会走到业务逻辑里。
注意:枚举值在 GraphQL 中默认是大写形式。如果你需要小写,可以在服务端做转换,但 schema 里建议保持大写,这是社区惯例。
列表类型:处理多个数据
列表类型用方括号表示,比如 [User] 表示「一组用户」。它告诉 GraphQL:这个字段返回的不是单个值,而是一个数组。
type Query {
users: [User]
posts: [Post!]
comments: [Comment!]!
}
这里有个容易混淆的地方:[User] 和 [User!] 和 [User]! 含义完全不同。
[User]:列表本身可以为 null,列表里的元素也可以为 null[User!]:列表本身可以为 null,但列表里的元素不能为 null[User]!:列表本身不能为 null,但列表里的元素可以为 null[User!]!:列表本身不能为 null,列表里的元素也不能为 null
嗯,这里要注意。我曾经在项目里用了 [User],结果数据库查询返回了 [null, null],前端遍历时直接报错。后来我改成 [User!]!,确保要么返回空数组,要么返回全是有效用户的数组,问题就解决了。
非空类型:用感叹号守住底线
非空类型,就是字段名后面的那个感叹号 !。它表示这个字段永远不会返回 null。如果 resolver 返回了 null,GraphQL 会直接报错。
type Article {
id: ID!
title: String!
content: String
author: User!
}
你看,id 和 title 后面有感叹号,表示它们必须有值。content 没有感叹号,表示可以为空(比如草稿文章可能没有内容)。author 有感叹号,表示每篇文章必须有一个作者。
我个人习惯,对于业务上「必须有」的字段,一律加非空标记。比如用户 ID、创建时间、主键等。这能帮你提前发现数据问题,而不是等到前端展示时才发现「咦,这里怎么是空的」。
实战建议:非空类型是 GraphQL 类型系统的「安全网」。多用非空,少用可选。但也要注意,如果字段在业务上确实可能为空(比如用户的中间名),就不要强行加非空,否则 resolver 会频繁报错。
五种类型如何配合使用?
实际项目中,这五种类型是混在一起用的。我给你看一个完整的例子:
enum OrderStatus {
PENDING
SHIPPED
DELIVERED
CANCELLED
}
type OrderItem {
productId: ID!
quantity: Int!
price: Float!
}
type Order {
id: ID!
status: OrderStatus!
items: [OrderItem!]!
total: Float!
createdAt: String!
}
type Query {
order(id: ID!): Order
ordersByStatus(status: OrderStatus!): [Order!]!
}
这个例子里,OrderStatus 是枚举,Order 和 OrderItem 是对象类型,ID、Int、Float、String 是标量类型,[OrderItem!]! 和 [Order!]! 是列表类型,几乎每个字段都用了非空类型。
你想想看,如果不用枚举,status 字段写成 String,那前端传个 "shipped" 还是 "SHIPPED" 就全靠默契了。如果不用非空,items 字段可能返回 null,前端还得做判空处理。类型系统把这些都管住了,开发效率自然就上去了。
我的经验:刚开始学 GraphQL 时,别急着用自定义标量类型(比如 Date、JSON)。先用好这五种基础类型,等项目复杂了再考虑扩展。我见过新手一上来就定义一堆自定义标量,结果序列化反序列化搞得很复杂,反而增加了维护成本。
好了,这五种类型就是 GraphQL 类型系统的基石。理解它们,你就能看懂 90% 的 GraphQL schema 了。下一章咱们聊聊如何用这些类型定义查询和变更操作,到时候你会看到它们是怎么真正跑起来的。