乐趣区

GraphQL —— 标量类型

标量 (ScalarTypeDefinition) 是 GraphQL 中不可分割的原子数据类型,在服务中充当叶子节点。对于客户端而言,合法的查询集 (Select Set) 必须到达叶子节点,也就是到达标量类型节点。
GraphQL 规范提供了五种标量:

Int: 32 位有符号整型,超出精度范围后,引擎会抛出异常

Float: 有符号双精度浮点数,超出精度范围后,引擎会抛出异常

String: 字符串,用于表示 UTF-8 字符序列

Boolean: bool 值

ID: 资源唯一标志符

1. ID 特性
上述五种类型与其他语言对应的类型定义相似,相信读者老爷们都已经非常熟悉,无需赘述,唯一值得探讨的是 ID 类型。

表现上 ID 类型只是一个字符串格式的值,引擎支持字符串解析值,也支持将 Int 解析值转换为字符串类型;
语义上 ”ID” 类型应该用于唯一标志一个资源对象,也就是说,使用相同 ID 值,无论查询多少次,结果都应该是同一对象,这一点有助于实现缓存,是 GraphQL 推荐的缓存方案;
引擎并不限制解析值的唯一性,查询结果包含多个 ID 值相同的节点是合法的。

我们来看一下例子加深印象:
[
// 字符串类型
{id: ‘1’},
// int 类型,引擎会将其转换为字符串
{id: 1},
// float 类型
// 非法值,引擎不支持 float 转换
// 将抛出 `TypeError` 错误
{id: 1.2},
// 与上面第一条重复
// 合法值,引擎并不强制 `ID` 值的唯一性
{id: ‘1’}
]
2. 自定义标量类型
除规范定义的标量外,还可以按需定义业务范畴内的标量。语法非常简单:
scalar Datetime
注意,这只是语义范畴定义,还需要定义序列化、反序列化函数:
new GraphQLScalarType({
name: “Datetime”,
description: “ 日期时间标量类型 ”,
// 序列化函数
serialize(value) {
return value.toString();
},
// 解析函数
parseValue(value) {
if (typeof value === “string”) {
return new Date(value);
}
throw new Error(“ 参数类型错误 ”);
},
// 解析函数
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
throw new Error(“ 参数类型错误 ”);
}
});
下面我们一个一个看这些配置:

name: 字段名,请保持与 schema 中定的标量类型名称保持一致

description: 类型描述,在一些诊断工具上还是很有用的

serialize: 序列化函数,用于将结果转换为适合 http 传输的数值类型

parseValue: 解析函数,用于将客户端通过 variables 参数传递的数值为 Date 类型

parseLiteral: 同样是解析函数,将客户端传递的 字面量参数 解析为 Date 类型

配置中的 parseValue、parseLiteral 两个函数功能上相似,都用于解析客户端参数,分别处理两种参数传递方式:
# variables 参数
# 引擎将调用 parseValue 函数
query (before: Datetime){
users(before: $before) {
id
name
}
}

variables {
before: “1991-02-19”
}

# 字面量参数
# 引擎将调用 parseLiteral 函数
query {
users(before: “1991-02-19”) {
id
name
}
}
最后说一些注意的点:

如果类型确定不会作为 InputType,可以省略 parseValue、parseLiteral。

parseValue 接收到的是 variables 对象中对应的值;而 parseLiteral 接收的则是引擎从 query 语句中解析出的 AST 节点。AST 节点内容形如:

{
// 字面量类型
“kind”: “StringValue”,
// 字面量值
“value”: “1991-02-19”,
// 指明字面量是否为 [BlockStringValue](https://facebook.github.io/graphql/June2018/#BlockStringValue()) 类型
“block”: false,
// token 位置
“loc”:
{
“start”: 18,
“end”: 30
}
}
3. 返回对象的标量
标量类型也支持返回结构化的对象,只要能为引擎提供符合规则的 serialize 函数,一切皆有可能。我们可以写出这样一个标量:
// Address 对象类型,不过这是一个标量
new GraphQLScalarType({
name: “Address”,
description: “ 对象类型的标量 ”,
serialize(value) {
// value 为对象类型
// value = {city: ‘ 深圳 ’, province: ‘ 广东省 ’, country: ‘ 中国 ’}
return value;
}
});
但是要注意,标量类型是 不可分割 的,不能再传入查询子集:
# 合法请求
query {
users {
id
name
# Address 类型值
bornOrigin
}
}
返回结果:
{
“data”: {
“users”: [
{
“id”: “1”,
“name”: “foo”,
“bornOrigin”: {
“city”: “ 深圳 ”,
“province”: “ 广东省 ”,
“country”: “ 中国 ”
}
}
]
}
}
完整代码在 此处。能做是一回事,该不该这么做,又是另一回事,返回对象的标量虽然合乎规则,但却违反了 按需加载 这一重要原则,没有实际意义也不值得推崇。
总结
标量是 GraphQL 中的原子类型,一般充当查询的叶子节点。GraphQL 规范提供了五种标量类型,其中 ID 最为特殊,用于唯一标志一个资源实例。在标准标量之外,也可以按需定义新的标量,规则如上。

退出移动版