本文于2023年6月27日星期二发表,Eddy Nguyen @ The Guild Blog
标量类型是要理解的最重要的GraphQL概念之一。知道标量如何工作并使用正确的工具可以帮助我们构建安全,健壮和可扩展的模式。在本文中,我们介绍了各种基本标量主题:
- 什么是GraphQl标量?
- 创建自定义GraphQl标量
- 类型安全的GraphQl标量工具
什么是GraphQL标量?
GraphQL标量类型是一种原始类型,它表示输入或输出字段返回类型中的值。 GraphQl带有一些本地标量:ID
,Int
,Float
,String
,Boolean
。但是,可以在模式中声明自定义标量以表示更复杂的数据类型。
标量值强制
GraphQl标量的一个重要概念是强制。当标量用作输入或输出时,就会发生这种情况。胁迫是采用一种或多种类型的传入价值的过程,并将其变成一种传出类型。
让我们以本机ID
标量为例:
- 用作输入时:客户端可以发送
string
或number
值,并且该值在到达服务器上的解析器之前将其胁迫到string
。 - 当用作输出时:服务器可以返回
string
或number
,并且在将其发送给客户端之前将其胁迫到string
。
这是本机标量输入和输出打字稿类型的完整列表:
标量 | 客户可以发送 | 解析器接收 | 解析器可以返回 | 客户收到 |
---|---|---|---|---|
id | string |
string |
string |
string |
number |
string |
number |
string |
|
int |
number (32位签名的整数) |
number |
string |
number (32位签名的整数) |
number (32位签名的整数) |
number (32位签名的整数) |
|||
boolean |
1 如果为true,0 如果false |
|||
float |
number (浮点或数字) |
number |
string |
number |
number |
number |
|||
boolean |
1 如果为true,0 如果false |
|||
字符串 | string |
string |
string |
string |
boolean |
“ true”,如果为true,则false”如果false | |||
number |
string (数字值转换为字符串) |
|||
布尔 | boolean |
boolean |
boolean |
boolean |
number |
false 如果传入值为0,则true 如果输入值不是0 |
自定义GraphQL标量
随着模式的发展,我们可能需要使用自定义验证规则呈现更复杂的数据类型。我们可以创建自定义GraphQl标量来求解此用例。
许多项目中使用了许多流行的自定义标量示例:
-
DateTime
:字符串表示确切的时间点 -
BigInt
:表示太大而无法由number
表示的值。类似于JavaScript的bigint
类型 -
EmailAddress
:字符串表示有效的电子邮件格式
有3个步骤来创建自定义GraphQl标量:
1.在模式中声明自定义标量
我们可以使用scalar
关键字来声明自定义GraphQl标量:
# schema.graphql
scalar CustomScalar
2.创建自定义标量解析器
现在,我们可以使用graphql
软件包的GraphQLScalarType
为标量创建一个分辨率:
# Install `graphql` package if it's not installed already
yarn add graphql
// resolvers/CustomScalar.ts
import { GraphQLScalarType } from "graphql";
export const CustomScalar = new GraphQLScalarType({
name: "CustomScalar",
description: "Custom Scalar description",
parseValue(inputValue: unknown) {
// (1) Used for input
},
parseLiteral(ast) {
// (2) Used for input
},
serialize(outputValue: unknown) {
// (3) Used for output
},
});
创建自定义标量时,有3个主要功能要牢记:
-
parseValue
:当标量用作输入中的变量时,称为此功能。该功能的验证和返回值传递给解析器。以下是graphQl操作的示例,该操作会触发parseValue
:
query Example($var: CustomScalar!) {
example(arg: $var) # a variable is used so `parseValue` is called on the server
}
-
parseLiteral
:当标量用作输入中的字面值时,此功能称为。ast
参数包含graphQl类(例如int或string)和输入的值。该功能的验证和返回值传递给解析器。以下是graphQl操作的示例,它将触发parseLiteral
:
query Example {
example(arg: "Hello") # a literal string is used so `parseLiteral` is called on the server
}
-
serialize
:在将值发送给客户端之前,在解析器返回的输出上调用此功能。由于大多数GraphQl服务器在HTTP上以JSON的形式发送结果,因此serialize
函数的返回值必须将值转换为string
,number
或boolean
。
查看标量值从客户端到服务器的流动流的可视化,然后返回客户端:
- 当使用标量用作输入时,分别使用
parseValue
或parseLiteral
解析变量或文字值。 - 解析值达到接收解析器的第二个参数(通常称为
args
)。 - 如果将标量用作输出,则该值到达接收解析器的第一个参数(通常称为
parent
)。 - 一旦准备返回客户端,标量值就会发送到标量解析器的
serialize
函数,以将其变成与JSON兼容的值。 - 序列化标量值已发送回客户端。
3.将自定义标量添加到解析器地图
最后,我们可以将自定义标量解析器添加到GraphQL Server中的Resololvers映射中。这是如何使用GraphQL Yoga进行的示例:
# Install `graphql-yoga` to create a GraphQL server
yarn add graphql-yoga
import { createYoga, createSchema } from "graphql-yoga";
import { createServer } from "http";
import { CustomScalar } from "./resolvers/CustomScalar.ts"; // 1. Import the custom resolver
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL typeDefs */,
resolvers: {
CustomScalar // 2. Put the custom scalar resolver into resolvers map
}
}),
});
const server = createServer(yoga);
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
});
现在,运行服务器允许我们在项目中使用自定义标量。 ð
GraphQL标量工具用于类型安全
GraphQL生态系统中有很多工具可以支持与本机和自定义标量一起工作:
1. GraphQl代码生成器
GraphQL Code Generator是一个具有广泛插件生态系统的CLI工具
对于标量,GraphQL代码生成器可以正确生成输入和输出类型。最重要的是,我们可以自定义本机和自定义类型的类型以满足我们的需求。
在GraphQL CodeGen插件的最新主要版本中,生成的Scalar
类型已更改为更好地支持标量输入和输出类型。此更改是必要的,因为它允许我们从现在开始更具表情的标量。
请继续阅读以查看如何使用代码生成clients和servers的类型。
客户端配置
typescript是生成基本打字稿类型的插件,包括本机和自定义标量的Scalars
类型。然后,此类型由typescript-operations等其他客户端插件使用。
首先,安装GraphQl代码生成器CLI和typescript
插件:
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript
这是一个示例配置,它为客户端生成本机标量类型和CustomScalar
标量
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema/types.generated.ts': {
plugins: ['typescript'],
config: {
scalars: {
// Recommended ID scalar type for clients:
ID: {
input: 'string | number',
output: 'string'
},
// Setting custom scalar type:
CustomScalar: {
input: 'string', // this means our server can take CustomScalar as string
output: 'number', // this means our server will return CustomScalar as number
},
},
},
},
},
};
export default config;
运行yarn graphql-codegen
生成以下标量类型:
// src/schema/types.generated.ts
export type Scalars = {
ID: { input: string | number; output: string };
String: { input: string; output: string };
Boolean: { input: boolean; output: boolean };
Int: { input: number; output: number };
Float: { input: number; output: number };
CustomScalar: { input: string; output: number };
};
默认情况下,typescript
插件生成了ID输入和输出的string
。这种方法允许插件用于客户端和服务器类型生成,而无需额外的配置。
另一方面,使用上面建议的配置来生成更接近GraphQl的本机行为的类型(即客户可以将string
或number
作为ID输入发送)可以使体验更好。许多系统都使用number
作为对象的ID类型。如果推荐的配置用于这些情况,我们不必在将其发送到服务器之前手动将ID转换为string
。
从typescript
和typescript-operations
插件V4开始,所有DepeDent客户端插件(例如Apollo客户端,React请求,URQL等)必须更新以使用新的输入/输出格式。如果您在这些客户端插件中发现问题,请在community repo中创建一个问题。
服务器的CodeGen配置
在typescript
和typescript-resolvers
插件v4.0.0中,默认ID标量输入类型从string
更改为string | number
。这样做是为了匹配预期的GraphQl客户端类型。
但是,诸如typescript-resolvers
之类的服务器插件也取决于typescript
插件生成的类型。在设置配置生成服务器类型时,这会引起很多摩擦。
在typescript
和typescript-resolvers
插件v4.0.1中,我们将默认ID标量输入类型恢复为string
。阅读pull request有关更多详细信息。
对于GraphQl服务器,我们可以将typescript-resolvers插件与typescript插件相同的scalars
配置。这种组合改变了标量类型的使用方式:
- 标量输入是在
parseValue
或parseLiteral
函数之后收到的胁迫值的类型。 - 标量输出是
serialize
之前返回的类型。
首先,安装GraphQl代码生成器CLI,typescript
和typescript-resolvers
插件:
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
这是推荐的配置服务器类型:
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema/types.generated.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
scalars: {
// Recommended ID scalar type for servers:
ID: {
input: 'string',
output: 'string | number',
},
// Setting custom scalar type:
CustomScalar: {
input: 'string', // this means our server can take CustomScalar as string
output: 'number', // this means our server will return CustomScalar as number
},
},
},
},
},
};
export default config;
运行yarn graphql-codegen
生成以下标量类型:
export type Scalars = {
ID: { input: string ; output: string | number };
String: { input: string; output: string };
Boolean: { input: boolean; output: boolean };
Int: { input: number; output: number };
Float: { input: number; output: number };
CustomScalar: { input: string; output: number };
};
推荐的服务器配置使处理ID标量类型更加方便。如果您具有具有数字ID的对象,则此方法特别有用。不使用推荐的配置,我们将必须创建自定义映射器或手动将数字转换为字符串以满足Typescript Typecheck。
推荐的设置适用于最常见的用例。它不涵盖一些可能使编写解析器尴尬的边缘案例。
例如,Int
的输出在技术上是string | number | boolean
,但默认为number
,因为大多数解析器永远不会返回string
或boolean
。
2. GraphQL标量(graphql-scalars
)
GraphQL Scalars是一个包含许多常用自定义GraphQl标量的库,例如DateTime
,BigInt
,等等。
即使创建自定义标量并不难,测试和发布标量在GraphQL服务器之间共享标量也可以迅速分散构建核心业务逻辑的注意力。因此,通常最好使用诸如graphql-scalars
的良好维护和记录的库。
这是我们可以从graphql-scalars
使用DateTime
标量的方法:
- 安装
graphql-scalars
:
yarn add graphql-scalars
- 在模式中声明
DateTime
:
# schema.graphql
scalar DateTime
- 将
DateTime
解析器导入到解析器图中:
import { createYoga, createSchema } from "graphql-yoga";
import { createServer } from "http";
import { DateTimeResolver } from "graphql-scalars"; // 1. Import scalar resolver
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL typeDefs */,
resolvers: {
DateTime: DateTimeResolver // 2. Put resolver to resolvers map
}
}),
});
const server = createServer(yoga);
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
});
- 使用GraphQL代码生成器创建类型:
graphql-scalars
的大多数标量解析器都带有可以在CodeGen Config中使用的推荐服务器类型。我们可以导入一个解析器并很容易使用上述类型:
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
import { DateTimeResolver } from "graphql-scalars"; // 1. Import scalar resolver
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema/types.generated.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
scalars: {
ID: {
input: 'string',
output: 'string | number',
},
DateTime: DateTimeResolver.extensions.codegenScalarType // 2. Use scalar resolver's recommended server type
},
},
},
},
};
export default config;
对于graphql-scalars
的自定义标量的推荐客户类型呢?
目前,没有官方软件包(尚未!)。但是,有一个issue on the GraphQL Scalars repo。
这是为graphql-scalars
选择正确的客户端类型的一般提示:
- 检查
serialize
函数的返回类型,然后在CodeGen Config. 中设置标量 - 如果您的客户端将返回的标量值转换为另一种类型,则可以在CodeGen配置中设置最终的
output
类型。
output
类型
3. GraphQL CodeGen Server预设
GraphQl代码生成器和graphql-scalars
库应该足以管理我们的标量实现和类型要求。但是,服务器预设(@eddeee888/gcg-typescript-resolver-files)可以进一步简化标量设置:
- 默认情况下使用推荐的ID标量服务器配置
- 检测是否在已安装的
graphql-scalars
软件包中存在自定义标量,然后自动将标量解析器导入解析器映射,并在CodeGen Config 中设置推荐类型
- 检测是否在已安装的
graphql-scalars
软件包中不存在自定义标量(或者如果未安装软件包),然后自动创建自定义标量解析器并将其放入解析器地图
- 安装GraphQl代码生成器CLI,服务器预设和((可选))GraphQl标量:
yarn add -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files
# (Optional) Install `graphql-scalars` if you intend to use custom scalars from this library
yarn add graphql-scalars
- 在CodeGen配置中使用服务器预设:
// codegen.ts
import type { CodegenConfig } from "@graphql-codegen/cli";
import { defineConfig } from "@eddeee888/gcg-typescript-resolver-files";
const config: CodegenConfig = {
schema: "**/schema.graphql",
generates: {
"src/schema": defineConfig(),
},
};
export default config;
运行yarn graphql-codegen
,就是这样!现在,每当将DateTime
之类的自定义标量添加到架构中时,实现和类型都会自动来自graphql-scalars
! ð
服务器预设不仅可以处理自定义标量。有现有的指南和博客文章,其中包含有关其功能和概念的更多详细信息:
- 阅读如何设置GraphQL Yoga / Apollo Server with Server Preset
- 阅读如何构建Scalable APIs with GraphQL Server Codegen Preset
概括
在本文中,我们探讨了GraphQL标量类型的重要性,其工作原理以及如何创建自定义标量解析器以扩展我们的模式。我们还探索了工具,以帮助使用本机和自定义标量(例如GraphQl Code Generator,GraphQL标量和服务器预设)。