前言

学一学 GraphQL
参考文章: https://chenyitian.gitbooks.io/graphql/content/introduction.html
参考网站: https://www.howtographql.com/basics/0-introduction/


GraphQL基础

1. 什么是GraphQL

GraphQL是一种用于API的查询语言,同时也是一种运行在服务端的执行引擎。它使用基于类型系统的模式定义和验证查询结构(类型系统由你的数据定义)。GraphQL不依赖任何特定数据库或存储引擎,而是通过现有代码和数据来支持查询

为什么使用GraphQL

  • 灵活的数据获取: 客户端可以请求确切的数据结构,避免数据过多或不足的问题
  • 单一端点: 所有查询通过一个端点完成,简化了客户端与服务端的交互
  • 强类型系统: 定义API时使用强类型,可以捕获开发错误,增强工具支持(如自动生成文档和代码)
  • 实时更新: 通过订阅(subscriptions),支持实时数据更新,客户端在数据变化时能自动接收更新

REST的对比

  • REST的对比 (优点)

    • 避免多次请求: REST中可能需要多次请求不同资源才能获取所需数据,而GraphQL能通过单一查询获取所有相关数据
    • 精确的数据获取: 客户端明确指定需要的字段,避免多余数据传输,适用于带宽有限或网络不稳定的环境
  • REST的对比 (缺点)

    • 查询解析复杂: 由于GraphQL查询的灵活性,解析和执行查询可能比REST更复杂,尤其在大规模数据集或复杂业务逻辑中
    • 缓存困难: 与REST固定URL不同,GraphQL查询不是静态的,因此标准HTTP缓存机制难以直接应用,需要额外设计缓存策略
    • 学习曲线: 😅

2. 字段Fields

GraphQL中,字段 (fields) 是定义在类型 (type) 上的属性,表示类型包含的数据内容。字段有以下特点

  • 每个字段都有名称和返回类型,类型可以是标量(如StringInt)、对象、枚举、列表等
  • 字段可以接受参数,用于传递附加信息或过滤数据

字段的基本定义

type User {
id: ID!
name: String!
age: Int
}

type Query {
user(id: ID!): User
}
  • User类型

    • 包含idnameage三个字段
    • 每个字段定义了User类型的数据结构
  • Query类型

    • 定义了一个查询字段user,可以根据id获取一个User

什么是Schema

  • SchemaGraphQL的核心概念,用于描述API的结构,包括
    • 类型定义Type: 定义API中的类型,例如标量类型 (StringInt)、对象类型 (User)、枚举类型等
    • 根类型Root Types: 包括QueryMutationSubscription,分别定义了查询、变更和订阅操作

解析器Resolver

  • 字段通常需要解析器来告诉GraphQL如何获取字段的值。当客户端请求某个字段时,GraphQL会调用对应的解析器来获取数据
const resolvers = {
Query: {
user: (parent, args, context, info) => {
const userId = args.id; // 从 args 获取用户 ID
return getUserById(userId); // 调用数据源函数获取用户数据
},
},
};

完整查询示例

  • 客户端可以发送如下查询请求
query {
user(id: "1") {
id
name
age
}
}
  • 定义Schema: 描述数据类型和操作
  • 编写Resolver: 实现具体的数据获取逻辑
  • 客户端查询: 通过Query请求数据,GraphQL调用解析器返回结果

Mutation示例

  • 下面是一个使用变更操作 (Mutation) 创建用户的示例
type User {
id: ID!
name: String!
age: Int
}

type Mutation {
createUser(name: String!, age: Int): User
}
  • 对应的解析器
const resolvers = {
Mutation: {
createUser: (parent, args, context, info) => {
const newUser = {
id: generateUniqueId(),
name: args.name,
age: args.age,
};
saveUserToDatabase(newUser); // 保存到数据库
return newUser;
},
},
};
  • 客户端请求
mutation {
createUser(name: "Alice", age: 30) {
id
name
age
}
}

3. 参数Arguments

GraphQL中,参数 (Arguments) 用于向字段或操作(如查询Query和变更Mutation)传递输入数据。参数允许客户端在请求中提供额外信息,用于筛选、过滤或指定要操作的数据

参数的定义

  • 参数可以定义在GraphQL schema中的字段上。参数可以是
    • 必需参数: 使用!符号表示,必须提供
    • 可选参数: 没有!符号的参数,可以省略
type User {
id: ID!
name: String!
age: Int
}

type Query {
user(id: ID!): User
users(age: Int): [User]
}
  • user字段有一个必需的id参数,用于指定要查询的用户
  • users字段有一个可选的age参数,用于筛选特定年龄的用户
  • 方括号[ ]表示返回值是一个列表类型

解析器中的参数

  • 在解析器 (Resolver) 中,参数通过args对象传递,可以使用这些参数来筛选或操作数据
const resolvers = {
Query: {
user: (parent, args, context, info) => {
// 根据 args.id 查询用户
return getUserById(args.id);
},
users: (parent, args, context, info) => {
// 如果提供了 age 参数,过滤用户
if (args.age) {
return getUsersByAge(args.age);
}
return getAllUsers();
},
},
};
  • args.idargs.age是客户端请求中传递的参数
  • user查询中,通过id获取单个用户
  • users查询中,根据age筛选用户列表,如果未提供age参数,则返回所有用户

客户端查询

  • 客户端可以通过查询请求传递参数
query {
user(id: "1") {
id
name
age
}

users(age: 30) {
id
name
age
}
}
  • 第一部分请求id"1"的用户
  • 第二部分请求所有age30的用户

变更中的参数

参数在变更操作(Mutation)中用于提供操作所需的输入数据,例如创建一个新用户

  • Schema定义
type Mutation {
createUser(name: String!, age: Int): User
}
  • 解析器
const resolvers = {
Mutation: {
createUser: (parent, args, context, info) => {
const newUser = {
id: generateUniqueId(),
name: args.name,
age: args.age,
};
saveUserToDatabase(newUser); // 将用户数据保存到数据库
return newUser;
},
},
};
  • 客户端请求
mutation {
createUser(name: "Alice", age: 30) {
id
name
age
}
}
  • 客户端传递参数nameage,服务器根据参数创建用户并返回新增用户的数据

GraphQL核心功能

4. 别名Aliases

GraphQL中,别名 (Aliases) 是一种用于重命名查询结果中字段名称的功能。它允许客户端

  • 避免命名冲突: 当查询结果包含多个同名字段时,使用别名区分不同的字段调用
  • 自定义响应格式: 通过别名修改返回数据中的字段名称,使其更符合客户端的需求

避免命名冲突

  • 当需要多次查询同一个字段的不同实例时,别名可以为每个字段调用提供不同的名称。例如
query {
user1: user(id: "1") {
id
name
age
}
user2: user(id: "2") {
id
name
age
}
}
  • user1user2是别名,用于区分user字段的两次调用, 这避免了因字段名称重复而导致的命名冲突

定义响应格式

  • 别名可以改变返回数据中的字段名称,使其更符合业务需求或提高可读性
{
"data": {
"user1": {
"id": "1",
"name": "Alice",
"age": 30
},
"user2": {
"id": "2",
"name": "Bob",
"age": 25
}
}
}
  • user1user2成为响应数据中的字段名称,分别包含两个不同用户的信息

5. 片段Fragments

片段 (Fragments) 是GraphQL中的一种机制,用于在多个查询或操作中重用一组字段选择。通过定义片段,可以减少字段的重复定义,提高查询的可读性和可维护性

片段的语法

fragment FragmentName on TypeName {
field1
field2
...
}
  • FragmentName: 片段的名称,用于在查询中引用片段
  • TypeName: 片段适用的类型,指定字段所属的对象类型
  • field1, field2, ...: 片段中包含的字段集合

片段的使用示例

假设我们有一个User类型,包含idnameage字段,以下示例展示了如何定义和使用片段

  • 片段定义
fragment UserFields on User {
id
name
age
}
  • 查询中使用片段
query {
user1: user(id: "1") {
...UserFields
}
user2: user(id: "2") {
...UserFields
}
}
  • 返回结果
{
"data": {
"user1": {
"id": "1",
"name": "Alice",
"age": 30
},
"user2": {
"id": "2",
"name": "Bob",
"age": 25
}
}
}

嵌套片段

片段可以嵌套使用,在一个片段中引用另一个片段,从而灵活地组织字段

  • 定义嵌套片段
fragment AddressFields on Address {
street
city
country
}

fragment UserFields on User {
id
name
age
address {
...AddressFields
}
}
  • 查询中使用嵌套片段
query {
user(id: "1") {
...UserFields
}
}
  • 返回结果
{
"data": {
"user": {
"id": "1",
"name": "Alice",
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
}
}
}

内联片段

内联片段 (Inline Fragments) 是GraphQL中的一种机制,用于在查询中根据对象的实际类型动态选择字段。它们主要用于处理接口 (Interface) 或联合类型 (Union Type),当返回的数据类型不确定时,可以根据具体类型选择不同的字段

  • 使用场景

    • 接口和联合类型: 如果查询的字段返回的是接口或联合类型,内联片段允许根据每种可能的类型指定不同的字段选择
    • 类型检查: 可用于在查询中执行类型检查,根据实际类型动态返回字段
  • 语法: 内联片段使用...后跟随on TypeName,然后在{}中指定该类型的字段

... on TypeName {
field1
field2
}
  • Schema示例: 假设有一个Person接口,两个实现类型EmployeeCustomer
interface Person {
id: ID!
name: String!
}

type Employee implements Person {
id: ID!
name: String!
salary: Int
}

type Customer implements Person {
id: ID!
name: String!
purchaseHistory: [String]
}

type Query {
persons: [Person]
}
  • 查询示例: 根据Person的具体类型选择不同的字段
query {
persons {
id
name
... on Employee {
salary
}
... on Customer {
purchaseHistory
}
}
}
{
"data": {
"persons": [
{
"id": "1",
"name": "Alice",
"salary": 50000
},
{
"id": "2",
"name": "Bob",
"purchaseHistory": ["item1", "item2"]
}
]
}
}

6. 操作名称

GraphQL中,操作名称 (Operation Name) 是一个可选的标识,用于明确标注特定的查询 (Query)、变更 (Mutation) 或订阅 (Subscription) 操作的名称。它有助于提高可读性、调试效率,以及在某些工具中实现特定功能

操作名称特点

  • 操作名称的位置
    • 紧跟在操作类型(如querymutationsubscription)之后
  • 命名规则
    • 名称只能包含字母、数字和下划线(_)
    • 名称不能以数字开头
  • 用途
    • 提供查询的上下文,明确查询意图
    • 在调试、日志记录和工具(如Apollo Client)中,用于识别特定的操作
    • 当一个请求中包含多个操作时,必须使用操作名称

操作名称示例

  • 查询示例: GetUser是操作名称,用于标识此查询操作
query GetUser {
user(id: "1") {
id
name
age
}
}
  • 变更示例: CreateUser是操作名称,表明此变更的目的为创建用户
mutation CreateUser {
createUser(name: "Alice", age: 30) {
id
name
age
}
}
  • 多操作示例: 当一个请求中包含多个操作时,必须使用操作名称进行区分
query GetUser {
user(id: "1") {
id
name
age
}
}

mutation UpdateUser {
updateUser(id: "1", name: "Alice") {
id
name
age
}
}
  • 匿名操作: 如果操作名称被省略,该操作称为匿名操作, 但在一个请求中不能包含多个匿名操作,因为没有操作名称用于区分
query {
user(id: "1") {
id
name
age
}
}

7. 操作类型

GraphQL中,操作类型 (Operation Type) 是指客户端可以在API请求中执行的三种操作: 查询 (Query)、变更 (Mutation) 和 订阅 (Subscription)。每种操作类型对应不同的用途,分别用于读取数据、修改数据和监听实时数据更新

操作类型介绍

  • 查询 Query
    • 用途: 用于读取和获取数据
    • 特点: 不会对服务器数据产生任何修改,仅返回请求的数据 (类似于REST APIGET请求)
query {
user(id: "1") {
id
name
age
}
}

<!-- 查询 user 数据,根据参数 id: "1" 获取特定用户的信息 -->
<!-- 返回的字段包括 id、name 和 age -->
  • 变更 Mutation
    • 用途: 用于修改服务器上的数据,包括创建、更新或删除操作
    • 特点: 与查询不同,变更操作会改变服务器的数据状态 (类似于REST APIPOSTPUTPATCHDELETE请求)
mutation {
createUser(name: "Alice", age: 30) {
id
name
}
}

<!-- 调用 createUser 变更操作,创建一个新用户 -->
<!-- 参数 name 和 age 提供新用户的基本信息 -->
<!-- 返回结果包括新用户的 id 和 name -->
  • 订阅 Subscription
    • 用途: 用于监听服务器上的事件,并在事件发生时实时接收更新
    • 特点: 用于实时数据更新场景,如实时聊天、股票行情等。订阅操作会保持一个持久的连接(通常使用WebSocket),当事件发生时,服务器会主动推送更新给客户端
subscription {
userAdded {
id
name
age
}
}

<!-- 订阅 userAdded 事件,当有新用户被添加时,服务器会推送更新数据 -->
<!-- 返回结果包含新用户的 id、name 和 age -->

三种操作类型的比较

操作类型用途是否修改数据使用场景
查询读取和获取数据获取用户、产品列表、详情页数据
变更修改服务器数据创建、更新、删除资源
订阅监听事件并实时接收数据更新实时聊天、通知、数据流

8. 查询Query

查询 (Query) 是GraphQL中的一种操作类型,用于从服务器获取数据。它与传统REST APIGET请求类似,但在灵活性和强大性方面超越了后者

查询的定义

  • GraphQL中,查询类型通常在schema中定义为Query类型。Query类型描述了所有可执行的查询操作
type Query {
user(id: ID!): User
users: [User]
}

type User {
id: ID!
name: String!
age: Int
}

查询解析器Resolver

  • 解析器负责实现字段的具体逻辑,定义如何获取数据。以下是查询解析器的示例
const users = [
{ id: '1', name: 'Alice', age: 30 },
{ id: '2', name: 'Bob', age: 25 },
];

const resolvers = {
Query: {
user: (parent, args, context, info) => {
return users.find(user => user.id === args.id);
},
users: () => users,
},
};
  • user: 根据传入的id参数,从users列表中查找并返回对应用户
  • users: 直接返回所有用户

查询的特点

  • 精确的数据获取: 客户端可以指定所需字段,避免传输多余数据
query {
user(id: "1") {
name
age
}
}
{
"data": {
"user": {
"name": "Alice",
"age": 30
}
}
}
  • 单一请求: 在一个请求中获取多个资源的数据
query {
user(id: "1") {
id
name
}
users {
id
name
}
}
{
"data": {
"user": {
"id": "1",
"name": "Alice"
},
"users": [
{
"id": "1",
"name": "Alice"
},
{
"id": "2",
"name": "Bob"
}
]
}
}

9. 变更Mutations

变更 (Mutations) 是GraphQL中的一种操作类型,专门用于对服务器端的数据进行修改操作。与查询 (Query) 仅用于读取数据不同,变更支持创建、更新或删除数据。每个变更操作都可以定义所需的输入参数和返回结果的结构

变更的定义

  • GraphQLschema中,变更类型通常定义在Mutation类型中,用来描述所有支持的数据修改操作
type Mutation {
createUser(name: String!, age: Int!): User
updateUser(id: ID!, name: String, age: Int): User
deleteUser(id: ID!): Boolean
}

type User {
id: ID!
name: String!
age: Int
}
  • createUser: 接收nameage参数,返回新创建的User对象
  • updateUser: 接收id(必需)、nameage参数,用于更新用户信息,返回更新后的User
  • deleteUser: 接收id参数,用于删除指定用户,返回布尔值表示操作是否成功

变更的解析器

  • 解析器定义了如何执行变更操作,通常与数据库或其他数据存储交互
const { v4: uuidv4 } = require('uuid');

const users = [
{ id: '1', name: 'Alice', age: 30 },
{ id: '2', name: 'Bob', age: 25 },
];

const resolvers = {
Mutation: {
createUser: (parent, args, context, info) => {
const newUser = {
id: uuidv4(), // 生成唯一 ID
name: args.name,
age: args.age,
};
users.push(newUser); // 添加到用户列表
return newUser;
},
updateUser: (parent, args, context, info) => {
const user = users.find(user => user.id === args.id);
if (!user) {
throw new Error("User not found");
}
if (args.name !== undefined) {
user.name = args.name;
}
if (args.age !== undefined) {
user.age = args.age;
}
return user;
},
deleteUser: (parent, args, context, info) => {
const userIndex = users.findIndex(user => user.id === args.id);
if (userIndex === -1) {
throw new Error("User not found");
}
users.splice(userIndex, 1); // 从列表中移除用户
return true;
},
},
};

变更的使用

客户端可以通过以下查询发送变更请求

  • 创建用户
mutation {
createUser(name: "Alice", age: 30) {
id
name
age
}
}
{
"data": {
"createUser": {
"id": "c81e728d-59f2-4d16-9f02-fcad84756789",
"name": "Alice",
"age": 30
}
}
}
  • 更新用户
mutation {
updateUser(id: "1", name: "Alice Updated") {
id
name
age
}
}
{
"data": {
"updateUser": {
"id": "1",
"name": "Alice Updated",
"age": 30
}
}
}
  • 删除用户
mutation {
deleteUser(id: "2")
}
{
"data": {
"deleteUser": true
}
}

10. 订阅Subscription

订阅 (Subscription) 是GraphQL中的一种操作类型,允许客户端订阅服务器上的事件,并在事件发生时实时接收更新数据。这种机制特别适合实时更新场景,如聊天消息、通知、股票价格等

订阅的工作原理

  • 建立订阅连接: 客户端发送订阅请求到服务器,指定要订阅的事件或数据
  • 保持连接: 服务器与客户端之间通过WebSocket或其他双向通信协议维持持久连接
  • 事件触发: 当服务器端的事件发生变化(如新增或更新数据)时,触发对应的订阅逻辑
  • 数据推送: 服务器将更新的数据通过连接推送到订阅的客户端

订阅示例

  • Schema定义
type User {
id: ID!
name: String!
age: Int
}

type Subscription {
userAdded: User
}

type Mutation {
createUser(name: String!, age: Int!): User
}
  • Resolvers实现
const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

const resolvers = {
Subscription: {
userAdded: {
// 定义订阅器,监听 USER_ADDED 事件
subscribe: () => pubsub.asyncIterator(['USER_ADDED']),
},
},
Mutation: {
createUser: (parent, args, context, info) => {
const newUser = {
id: generateUniqueId(),
name: args.name,
age: args.age,
};

// 保存用户数据(模拟数据库操作)
saveUserToDatabase(newUser);

// 触发 USER_ADDED 事件
pubsub.publish('USER_ADDED', { userAdded: newUser });

return newUser;
},
},
};
  • 客户端订阅示例
subscription {
userAdded {
id
name
age
}
}
  • 当有新用户通过createUser变更操作添加时,服务器会触发userAdded事件,客户端将接收到用户信息

Apollo Client订阅

  • 配置Apollo Client: 创建一个支持查询、变更和订阅的客户端
import { ApolloClient, InMemoryCache, split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';

// 配置 HTTP 链接用于查询和变更
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });

// 配置 WebSocket 链接用于订阅
const wsLink = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true,
},
});

// 使用 split 根据操作类型分别选择链接
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);

// 创建 Apollo 客户端
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
  • 订阅操作: 使用Apollo ClientuseSubscription Hook实现订阅
import { gql, useSubscription } from '@apollo/client';

const USER_ADDED_SUBSCRIPTION = gql`
subscription OnUserAdded {
userAdded {
id
name
age
}
}
`;

function UserList() {
const { data, loading, error } = useSubscription(USER_ADDED_SUBSCRIPTION);

if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;

return (
<div>
<h2>New User Added:</h2>
<p>ID: {data.userAdded.id}</p>
<p>Name: {data.userAdded.name}</p>
<p>Age: {data.userAdded.age}</p>
</div>
);
}

export default UserList;
  • 总而言之,GraphQLSubscription依赖于WebSocket来实现。WebSocket提供了双向持久连接的能力,使得服务器能够在事件发生时立即向客户端推送数据,而Subscription则定义了这些事件和数据的结构。因此,Subscription不是替代WebSocket,而是利用WebSocket来实现特定的功能和需求

GraphQL进阶

11. 变量 Variables

GraphQL中,变量 (Variables) 是一种机制,用于动态传递参数到查询 (Query)、变更 (Mutation) 或订阅 (Subscription) 中。通过使用变量,可以使查询更加灵活、可重用,并且避免将用户输入直接嵌入到查询字符串中,从而提升安全性和可读性

变量的定义与使用

  • 查询中的变量: 变量需要在查询语句中定义,并在执行查询时传入实际的值
query GetUser($id: ID!) {
user(id: $id) {
id
name
age
}
}

<!-- $id 是一个变量,表示用户的 ID -->
<!-- 变量的类型为 ID!,! 表示这是一个必需字段 -->
  • 变量在React中的使用: 使用Apollo ClientuseQuery Hook动态传递变量值
import { gql, useQuery } from '@apollo/client';

// 定义查询和变量
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
age
}
}
`;

function User({ userId }) {
const { loading, error, data } = useQuery(GET_USER, {
variables: { id: userId }, // 动态传递变量值
});

if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;

return (
<div>
<h2>User Details</h2>
<p>ID: {data.user.id}</p>
<p>Name: {data.user.name}</p>
<p>Age: {data.user.age}</p>
</div>
);
}

export default User;

// 变量`$id`的值由组件的`props`动态传递
// 查询使用`useQuery Hook`,并通过`variables`选项传递实际的变量值
  • 变更中的变量: 在变更操作中,变量同样可以动态传递参数
mutation CreateUser($name: String!, $age: Int!) {
createUser(name: $name, age: $age) {
id
name
age
}
}
  • Apollo Client示例
import { gql, useMutation } from '@apollo/client';

const CREATE_USER = gql`
mutation CreateUser($name: String!, $age: Int!) {
createUser(name: $name, age: $age) {
id
name
age
}
}
`;

function CreateUserForm() {
const [createUser, { data, loading, error }] = useMutation(CREATE_USER);

const handleSubmit = async (e) => {
e.preventDefault();
const name = e.target.name.value;
const age = parseInt(e.target.age.value, 10);

await createUser({ variables: { name, age } });
};

if (loading) return <p>Submitting...</p>;
if (error) return <p>Error: {error.message}</p>;

return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="age" placeholder="Age" type="number" required />
<button type="submit">Create User</button>
{data && <p>Created User: {data.createUser.name}</p>}
</form>
);
}

export default CreateUserForm;

12. 指令 Directives

指令 (Directives) 是GraphQL中的一种强大功能,用于动态影响查询、变更 (Mutation) 或订阅 (Subscription) 的执行行为。指令可以应用于字段、片段等,帮助客户端或服务器灵活调整查询结果的生成逻辑。例如,可以根据条件决定是否包含某字段或动态修改返回结果

内置指令

GraphQL规范中定义了两个常用的内置指令: @include@skip

  • @include: 条件包含
    • 用于根据条件动态包含字段或片段
    • 接受一个名为if的参数,该参数的值为布尔类型
query GetUser($withAge: Boolean!) {
user(id: "1") {
id
name
age @include(if: $withAge)
}
}

<!-- 如果变量 $withAge 为 true,则返回结果中包含 age 字段 -->
<!-- 如果 $withAge 为 false,则排除 age 字段 -->
  • @skip: 条件跳过
    • 用于根据条件跳过字段或片段
    • 同样接受一个名为if的布尔参数
query GetUser($withoutAge: Boolean!) {
user(id: "1") {
id
name
age @skip(if: $withoutAge)
}
}

<!-- 如果变量 $withoutAge 为 true,则跳过 age 字段 -->
<!-- 如果 $withoutAge 为 false,则包含 age 字段 -->

自定义指令

除了内置指令,GraphQL还支持自定义指令。这些指令可以用于更复杂的场景,比如数据验证、字段格式化等。自定义指令需要在schema中声明,并提供对应的实现逻辑

  • 自定义指令声明
directive @upper on FIELD_DEFINITION

type Query {
greeting: String @upper
}

<!-- @upper 是一个自定义指令,应用在字段定义上 -->
<!-- on FIELD_DEFINITION 指定了该指令适用于字段定义 -->
  • 自定义指令的实现: 使用graphql-tools创建一个@upper指令,将字符串字段的返回值转换为大写
const { SchemaDirectiveVisitor } = require('graphql-tools');
const { defaultFieldResolver } = require('graphql');

class UpperCaseDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;

field.resolve = async function (...args) {
const result = await resolve.apply(this, args);
if (typeof result === 'string') {
return result.toUpperCase();
}
return result;
};
}
}

module.exports = UpperCaseDirective;
  • 整合指令到Schema: 自定义指令@upper应用于greeting字段,返回的值将自动转换为大写
const { gql } = require('apollo-server');
const UpperCaseDirective = require('./UpperCaseDirective');

const typeDefs = gql`
directive @upper on FIELD_DEFINITION

type Query {
greeting: String @upper
}
`;

const resolvers = {
Query: {
greeting: () => "hello world",
},
};

const { makeExecutableSchema } = require('graphql-tools');

const schema = makeExecutableSchema({
typeDefs,
resolvers,
schemaDirectives: {
upper: UpperCaseDirective,
},
});

module.exports = schema;
  • 客户端使用示例
query {
greeting
}
{
"data": {
"greeting": "HELLO WORLD"
}
}

附录

参考文章: https://chenyitian.gitbooks.io/graphql/content/introduction.html
参考网站: https://www.howtographql.com/basics/0-introduction/