ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

type-graphql 联合类型(Unions)实战指南:用 createUnionType 定义与解析多态返回类型

type-graphql 联合类型(Unions)实战指南:用 createUnionType 定义与解析多态返回类型 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载导读GraphQL 的联合类型Union Type允许一个字段在运行时返回多种不同类型的对象是构建灵活 API如全局搜索、多态资源列表的利器。本文以 type-graphql 框架为背景完整讲解如何用createUnionType将多个ObjectType()类组合成 GraphQL union、如何在Query返回值中安全使用它以及当解析器返回普通对象plain object时如何通过自定义resolveType正确识别具体类型。读完本文你将能独立实现一个可运行的电影/演员混合搜索联合类型查询并理解其背后的元数据收集与 schema 生成原理。什么时候需要 Union Type有些 API 场景下一个字段的返回类型并不是固定的而是可能类型集合中的一种。例如一个影视站点的搜索功能用同一句搜索词去匹配数据库中的电影Movie与演员Actor查询结果需要既能返回Movie又能返回Actor。此时就适合用 GraphQL 的 Union Type 来表达这种多选一的返回语义。联合类型在 GraphQL 层面的关键特点联合类型本身不声明公共字段字段必须通过内联片段inline fragment按具体类型选取运行时由resolveType或 graphql-js 的类型判定机制决定返回值到底属于哪一个成员类型。type-graphql 把这一机制封装为createUnionType辅助函数源码见 src/decorators/unions.ts让开发者用 TypeScript 类与装饰器就能声明联合类型。第一步定义成员 Object Type联合类型的成员必须是对象类型。沿用影视搜索的例子先用ObjectType()与Field()定义两个成员类ObjectType() class Movie { Field() name: string; Field() rating: number; }ObjectType() class Actor { Field() name: string; Field(type Int) age: number; }要点说明每个成员都必须是带ObjectType()装饰的类type-graphql 会据此为其生成对应的 GraphQL Object TypeField(type Int)显式声明数值类型为 GraphQL 的Int否则默认映射为FloatInt需从type-graphql导入成员类可以拥有完全不同的字段集合——这正是联合类型的价值所在Movie有ratingActor有age二者互不干扰。第二步用 createUnionType 组合联合类型有了成员类后调用createUnionType生成 unionimport { createUnionType } from type-graphql; const SearchResultUnion createUnionType({ name: SearchResult, // GraphQL 中 union 的名称 types: () [Movie, Actor] as const, // 返回对象类型类元组的函数 });从 type-graphql 源码src/decorators/unions.ts可以看到createUnionType的完整配置结构配置项类型说明namestringGraphQL schema 中 union 的名字必填需在 schema 内唯一descriptionstring可选union 的类型描述会写入 schema 的 descriptiontypes() readonly ClassType[]惰性函数返回成员对象类型类的元组建议使用as const声明为只读元组resolveTypeTypeResolver可选自定义返回值 → 具体成员类型的判定函数types 为什么是函数而不是数组types以惰性函数thunk形式提供而不是直接给数组。这与 schema 生成阶段先构建所有 Object Type、再构建依赖它们的 Union Type的顺序有关在 src/schema/schema-generator.ts 中union 的typesThunk会在对象类型信息构建完成后才被调用从而通过闭包从objectTypesInfoMap中取出各成员对应的GraphQLObjectType避免循环依赖与类型尚未就绪的问题。as const元组语法的作用types: () [Movie, Actor] as const中的as const是让 TypeScript 把数组推断为只读元组。这是为了让框架能够精确推导出联合的 TS 类型。createUnionType的泛型签名src/decorators/unions.ts利用UnionFromClassesTsrc/helpers/utils.ts将成员类的实例类型逐一取出并合并为联合InstanceTypeMovie | InstanceTypeActor最终使typeof SearchResultUnion等价于Movie | Actor。底层发生了什么Symbol 与元数据收集createUnionType内部并不直接创建GraphQLUnionType而是调用getMetadataStorage().collectUnionMetadata(...)将名称、描述、成员类函数与resolveType存入全局元数据存储src/metadata/metadata-storage.ts并为该 union 生成一个以名字命名的Symbol作为返回值。随后buildSchema在生成 schema 时src/schema/schema-generator.ts遍历metadataStorage.unions为每个 symbol 构造真正的GraphQLUnionType。这也是必须在装饰器返回值注解中显式传入SearchResultUnion这个值这一使用约定的根源——它既是 TS 类型标记又是元数据存储的键。第三步在 Query 中使用联合类型定义好 union 后即可在解析器中将其作为返回类型Resolver() class SearchResolver { Query(returns [SearchResultUnion]) async search(Arg(phrase) phrase: string): PromiseArraytypeof SearchResultUnion { const movies await Movies.findAll(phrase); const actors await Actors.findAll(phrase); return [...movies, ...actors]; } }几个必须注意的细节返回类型注解必须显式使用SearchResultUnion值由于 TypeScript 的反射机制无法从PromiseArraytypeof SearchResultUnion这样的类型注解自动推导出 GraphQL 类型Query(returns [SearchResultUnion])中的这个值承担了告知框架返回类型的重任。这一限制在原文档中已被明确提示。typeof SearchResultUnion提供编译期类型安全它等于 TS 联合类型Movie | Actor因此数组字面量可以同时包含两类实例而无需as any。字段参数仍可正常使用装饰器示例中Arg(phrase)照常工作union 只影响返回类型部分。在仓库的完整示例 examples/enums-and-unions/resolver.ts 中可以看到同样的写法Query(_returns [SearchResult])配合PromiseArraytypeof SearchResult而 union 定义位于 examples/enums-and-unions/search-result.union.ts由Recipe与Cook两个成员组成。解析具体返回类型默认行为与自定义 resolveType默认行为返回类实例联合类型解析的关键在于查询执行时graphql-js 必须知道每个返回值到底属于哪个成员类型。type-graphql 的默认策略是——要求解析器返回具体对象类型类的实例。在 src/schema/schema-generator.ts 中未提供自定义resolveType时框架通过instance instanceof ObjectClassType遍历成员类找到匹配的类后返回其 GraphQL 类型名。这意味着如果解析器返回的是普通 JS 对象plain object而不是new Movie()/new Actor()之类的类实例instanceof判定会失败框架将抛出UnionResolveTypeError错误信息见 src/errors/UnionResolveTypeError.tsCannot resolve type for union ... You need to return instance of object type class, not a plain object!。自定义 resolveType允许返回普通对象如果业务代码更习惯返回普通数据对象例如直接从数据库/ORM 映射得到的结果可以在createUnionType中提供自己的resolveType通过检查数据形态来判定类型const SearchResultUnion createUnionType({ name: SearchResult, types: () [Movie, Actor] as const, // 自定义返回对象类型的检测实现 resolveType: value { if (rating in value) { return Movie; // 返回对象类型类带有 ObjectType() 的那个 } if (age in value) { return Actor; // 或者返回类型的 schema 名称字符串 } return undefined; }, });这里resolveType支持两种返回值对象类型类本身如Movie框架会将其映射到对应的 GraphQL 类型名schema 名称字符串如Actor直接作为 GraphQL 类型名返回。从源码 src/schema/schema-generator.ts 可以看到getResolveTypeFunction对返回值的归一化处理若resolveType返回的既不是空值也不是字符串则通过possibleObjectTypesInfo.find(objectType objectType.target resolvedType)?.type.name把类引用转换为类型名字符串。此外该函数是异步包装的所以resolveType中也可以执行异步逻辑例如查库后再判定。常见的判定模式就是示例中的形状嗅探rating in value判定为Movieage in value判定为Actor。也可以按业务唯一标识、__typename字段或任何可靠字段来判断。务必保证所有成员都能被覆盖到避免返回undefined导致类型无法解析。两种策略的取舍策略解析器返回值resolveType适用场景默认instanceof必须返回对象类型类实例无需提供解析器中直接构造/返回类实例自定义resolveType可以是普通对象必须自行实现判定数据来自 ORM、数据库映射、序列化结果客户端如何消费联合类型查询示例联合类型没有公共字段客户端必须使用内联片段按具体类型取字段。最终 schema 构建完成后即可发起如下查询query { search(phrase: Holmes) { ... on Actor { # 也许搜到的是 Katie Holmes name age } ... on Movie { # 那一定搜到了 Sherlock Holmes name rating } } }行为说明每个返回值只会命中一个内联片段——graphql-js 依据resolveType或默认instanceof判定确定的实际类型来匹配在片段内还可以继续选取该类型独有的字段如Actor.age、Movie.rating开发调试时也可以在字段里加上__typename直观确认每个结果被判定成了哪个类型。仓库示例 examples/enums-and-unions/examples.graphql 中提供了一个真实可复用的搜索查询SearchByCookName它使用__typename加... on Recipe/... on Cook片段展示了联合类型查询的标准写法。完整运行示例若想在本地完整跑通定义 union → 构建 schema → 查询的链路可以直接参考仓库中的 examples/enums-and-unions 示例其入口 examples/enums-and-unions/index.ts 展示了标准启动流程import reflect-metadata; import path from node:path; import { ApolloServer } from apollo/server; import { startStandaloneServer } from apollo/server/standalone; import { buildSchema } from type-graphql; import { ExampleResolver } from ./resolver; async function bootstrap() { // 构建 type-graphql 可执行 schema const schema await buildSchema({ resolvers: [ExampleResolver], // 将 schema 定义写入当前目录的 schema.graphql 文件 emitSchemaFile: path.resolve(__dirname, schema.graphql), }); const server new ApolloServer({ schema }); const { url } await startStandaloneServer(server, { listen: { port: 4000 } }); console.log(GraphQL server ready at ${url}); } bootstrap().catch(console.error);运行后即可通过schema.graphql看到生成的 union 定义形如union SearchResult Recipe | Cook再用上述查询语句在 Playground 或客户端中验证解析结果。该示例同时演示了 enum 与 union 的组合使用Recipe.preparationDifficulty使用Difficulty枚举适合作为进阶参考。小结本文围绕 type-graphql 的联合类型特性梳理了完整的实践链路用ObjectType()定义成员类字段各自独立用createUnionType({ name, types, resolveType? })组合成 union其中types使用惰性函数加as const元组以保证类型推导框架层面由元数据存储与 schema 生成器配合实现见 src/decorators/unions.ts、src/metadata/metadata-storage.ts、src/schema/schema-generator.ts在Query(returns [SearchResultUnion])中显式使用 union 值作为返回类型注解并用typeof SearchResultUnion保持编译期类型安全解析器要么返回对象类型类实例默认instanceof判定要么提供自定义resolveType以支持普通对象并注意让判定逻辑覆盖所有成员类型客户端通过内联片段按类型取字段必要时用__typename辅助调试。理解默认instanceof判定与自定义resolveType两条路径的区别是避免在联合类型解析时遇到UnionResolveTypeError的关键而理解createUnionType返回值是元数据存储中的 Symbol 这一设计则能解释为什么它必须同时出现在装饰器注解与 TS 类型推导两个层面。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐SurfSense mcp_discovery 子智能体深度解析基于 MCP 与原生集成的 Connected-apps SpecialistSurfSense mcp_discovery 子智能体深度解析基于 MCP 与原生集成的 Connected apps Specialist 本篇文章围绕后端GraphQLAPI设计type-graphql 中的 Union 类型使用 createUnionType 定义多态查询返回类型type graphql 中的 Union 类型使用 createUnionType 定义多态查询返回类型 type graphql 允许 API 返回一个“后端GraphQLAPI设计BiSheng ReBAC 权限引擎核心F004端到端验证指南OpenFGA 初始化、双写补偿与回归检查BiSheng ReBAC 权限引擎核心F004端到端验证指南OpenFGA 初始化、双写补偿与回归检查 本文是 BiSheng v2.5.0 中 F00后端GraphQLAPI设计上一篇EIP-7825 交易 Gas 上限 2^24 实证分析基于 EIPs 仓库 analysis.md 的以太坊主网数据解读下一篇免费音乐解锁终极指南Unlock Music 浏览器本地解密全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表