NestJS 9 GraphQL 中文文档(八) - 联合体和枚举

260 阅读4分钟

写在前面的话

Nest 最新的版本为V9,较之前的V7/8有不小改动,GraphQL 部分官方文档之前的V8也是本人翻译的,有很多不完善的地方,这次打算重新精细翻译一遍,会持续更新这块内容,并最后贡献给中文文档仓库,为中文社区贡献一份力量。有兴趣的小伙伴记得要关注收藏。

联合体

联合体类型与接口很相似,但是他们没有指定类型之间的任何公共字段(在此处阅读更多)。联合体对于从单个字段返回不相交的数据类型很有用。

代码优先

要定义一个GraphQL联合体类型,我们必须定义这个联合体将由哪些类组成。按照Apollo文档中的示例,我们将创建两个类。首先,Book

import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Book {
  @Field()
  title: string;
}

然后是Author

import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Author {
  @Field()
  name: string;
}

在这里,使用从@nestjs/graphql包里导出的createUnionType函数来注册一个ResultUnion联合体。

export const ResultUnion = createUnionType({
  name: 'ResultUnion',
  types: () => [Author, Book] as const,
});

警告⚠️
createUnionType函数中的types属性返回的数组应该被赋予一个常量断言。如果该常量断言没有提供,在编译的时候就会生成一个错误的声明文件,并且当其他项目使用此文件时就会报错。

现在,我们可以在查询中引用这个ResultUnion了:

@Query(returns => [ResultUnion])
search(): Array<typeof ResultUnion> {
  return [new Author(), new Book()];
}

最终在SDL中生成GraphQL schema的以下部分:

type Author {
  name: String!
}

type Book {
  title: String!
}

union ResultUnion = Author | Book

type Query {
  search: [ResultUnion!]!
}

库生成的默认resolveType() 函数将根据解析器方法返回的值提取类型。这意味着必须返回类实例而不是JavaScript对象字面量。

要提供一个自定义的resolveType() 函数,需要给createUnionType() 函数传入的可选对象传递一个resolveType 属性,如下所示:

export const ResultUnion = createUnionType({
  name: 'ResultUnion',
  types: () => [Author, Book] as const,
  resolveType(value) {
    if (value.name) {
      return Author;
    }
    if (value.title) {
      return Book;
    }
    return null;
  },
});

模式优先

要在模式优先方式中定义联合体,只需用SDL创建一个GraphQL 联合体即可。

type Author {
  name: String!
}

type Book {
  title: String!
}

union ResultUnion = Author | Book

然后,你可以使用类型生成功能(如快速入门章节中所示)来生成对应的TypeScript定义:

export class Author {
  name: string;
}

export class Book {
  title: string;
}

export type ResultUnion = Author | Book;

联合体在解析器映射中需要一个额外的 __resolveType 字段来确定该联合体应该解析为哪个类型。另外,请注意ResultUnionResolver 类必须在任意模块中被注册为提供者。让我们创建一个ResultUnionResolver 类并定义 __resolveType 方法:

@Resolver('ResultUnion')
export class ResultUnionResolver {
  @ResolveField()
  __resolveType(value) {
    if (value.name) {
      return 'Author';
    }
    if (value.title) {
      return 'Book';
    }
    return null;
  }
}

提示
所有的装饰器都是从@nestjs/graphql包里导出的。

枚举

枚举类型是一种特殊的标量,仅限于一组特定的允许值(在此处阅读更多)。这使你可以:

  • 验证此类型的任何参数都是一个允许值
  • 通过类型系统传达一个字段将始终是一组有限值中的一个

代码优先

当使用代码优先方式时,你可以通过创建一个TypeScript 枚举来定义一个GraphQL 枚举类型。

export enum AllowedColor {
  RED,
  GREEN,
  BLUE,
}

有了这个之后,接着用@nestjs/graphql包里导出的 registerEnumType来注册AllowedColor枚举:

registerEnumType(AllowedColor, {
  name: 'AllowedColor',
});

现在你可以在类型中引用AllowedColor了:

@Field(type => AllowedColor)
favoriteColor: AllowedColor;

最终在SDL中生成GraphQL schema的以下部分:

enum AllowedColor {
  RED
  GREEN
  BLUE
}

要为枚举提供描述,传递description 属性到registerEnumType函数即可。

registerEnumType(AllowedColor, {
  name: 'AllowedColor',
  description: 'The supported colors.',
});

要为枚举值提供描述,或标记一个值为已废弃,传入valuesMap属性即可,如下所示:

registerEnumType(AllowedColor, {
  name: 'AllowedColor',
  description: 'The supported colors.',
  valuesMap: {
    RED: {
      description: 'The default color.',
    },
    BLUE: {
      deprecationReason: 'Too blue.',
    },
  },
});

最终在SDL中生成GraphQL schema的以下部分:

"""
The supported colors.
"""
enum AllowedColor {
  """
  The default color.
  """
  RED
  GREEN
  BLUE @deprecated(reason: "Too blue.")
}

模式优先

要在模式优先方式中定义枚举,只需用SDL创建GraphQL 枚举:

enum AllowedColor {
  RED
  GREEN
  BLUE
}

然后你可以使用类型生成功能(如快速入门章节所示)来生成对应的TypeScript定义:

export enum AllowedColor {
  RED
  GREEN
  BLUE
}

有时候,后端会在内部强制使用与公共API中不同的枚举值。在这个例子中,API包含RED,但在解析器中我们可能使用的是 #f00(在此处阅读更多)。要做到这点,需要为AllowedColor枚举声明一个解析器对象:

export const allowedColorResolver: Record<keyof typeof AllowedColor, any> = {
  RED: '#f00',
};

提示
所有装饰器都是从@nestjs/graphql包里导出的。

然后将此解析器对象与GraphQLModule@forRoot() 方法的解析器属性一起使用,如下所示:

GraphQLModule.forRoot({
  resolvers: {
    AllowedColor: allowedColorResolver,
  },
});