typed-graphqlify 内联片段与判别联合:onUnion 如何让类型收窄更智能?
typed-graphqlify 内联片段与判别联合onUnion 如何让类型收窄更智能【免费下载链接】typed-graphqlifyBuild Typed GraphQL Queries in TypeScript without the code generation项目地址: https://gitcode.com/gh_mirrors/ty/typed-graphqlify在 TypeScript 中手写 GraphQL 查询时最头疼的莫过于处理内联片段Inline Fragment与判别联合Discriminated Union同一个字段可能返回多种类型而类型却始终是模糊的。typed-graphqlify作为一款无需代码生成、纯 TypeScript 构建类型化 GraphQL 查询的工具用on和onUnion两个小帮手解决了这个难题。本文将带你一步步搞懂onUnion的原理看看它如何让类型收窄Type Narrowing变得聪明又省心从此告别any与手写联合类型的痛苦。什么是 GraphQL 内联片段新手也能看懂GraphQL 的接口Interface和联合类型Union允许一个字段返回多种对象类型。比如经典的《星球大战》例子hero字段可能返回Droid机器人也可能返回Human人类两者拥有的字段完全不同。为了分别查询各自特有的字段就需要使用内联片段语法query getHeroForEpisode { hero { id ... on Droid { primaryFunction } ... on Human { height } } }... on Droid和... on Human就是内联片段——只有在 hero 实际返回对应类型时这些字段才会生效。这是 GraphQL 处理多态数据的标准姿势也是 typed-graphqlify 必须支持的基础能力。为什么需要判别联合一个常见的类型痛点回到 TypeScript 世界内联片段带来了一个棘手问题查询结果的类型该如何表达如果你用传统方式手写要么写成any要么手动拼一个联合类型interface Hero { id: number primaryFunction?: string height?: number }这种全部可空的写法毫无类型安全保障——想调用primaryFunction时TypeScript 根本不会提醒你先判断 hero 到底是不是 Droid。这正是判别联合要解决的场景给每个类型加上一个判别字段如kind让 TypeScript 能根据字段值自动收窄类型。on 与 onUnion一次看懂两个内联片段帮手typed-graphqlify 提供了两个处理内联片段的 API位于 src/types.ts 中on(typeName, fields)定义单个内联片段用...on(Droid, {...})的展开形式挂到查询对象上onUnion({ 类型A: 字段, 类型B: 字段 })一次性定义多个内联片段并自动推导出判别联合类型。先用基础版on写一遍import { on, query, types } from typed-graphqlify query(getHeroForEpisode, { hero: { id: types.number, ...on(Droid, { primaryFunction: types.string }), ...on(Human, { height: types.number }), }, })这段代码能正确渲染出 GraphQL 内联片段但它的 TypeScript 类型是堆叠在一起的TypeScript 并不清楚Droid和Human是互斥的两个分支。要获得真正的类型收窄还得靠onUnion。onUnion 实战三步实现智能类型收窄onUnion的核心思路是把每种类型的**判别字段discriminator**和专属字段写在一起typed-graphqlify 会自动把这些片段合并成A | B的联合类型。来看官方示例import { onUnion, query, types } from typed-graphqlify query(getHeroForEpisode, { hero: { id: types.number, ...onUnion({ Droid: { kind: types.constant(Droid), primaryFunction: types.string, }, Human: { kind: types.constant(Human), height: types.number, }, }), }, })这里有两个关键细节值得新手注意kind是判别字段用types.constant(Droid)声明为字面量类型Droid它是后续类型收窄的钥匙生成的类型是判别联合hero的类型会自动推导为{ kind: Droid, primaryFunction: string } | { kind: Human, height: number }。接下来查询结果的类型收窄就完全交给 TypeScript 了const hero queryResult.hero if (hero.kind Droid) { // 此处 hero 已被收窄为 Droid 类型可安全访问 primaryFunction console.log(hero.primaryFunction) } else { // 此处 hero 自动收窄为 Human可安全访问 height console.log(hero.height) }当hero.kind Droid时TypeScript 会自动把hero收窄到 Droid 分支访问primaryFunction不再报错进入else分支后又自动收窄到 Human。整个过程零手写联合类型、零类型断言typeof queryResult.hero直接可用——这正是让类型收窄更智能的含义。深入源码onUnion 的实现原理onUnion的实现非常精妙本质上是on的语法糖。核心代码在 src/types.tsexport function onUnionT(types: Recordstring, T): T { let fragments: Recordany, T {} for (const [typeName, internal] of Object.entries(types)) { fragments { ...fragments, ...on(typeName, internal) } } return fragments as any }它遍历传入的每种类型调用on(typeName, internal)生成带 Symbol 标记的内联片段对象再合并展开返回。这些 Symbol 片段在渲染阶段被 src/render.ts 中的renderInlineFragment识别最终输出成合法的... on Droid { ... }语法。类型层面的推导则依靠types.constant返回的字面量类型——这也是为什么onUnion的判别字段必须用types.constant而不是types.string否则kind会是宽泛的string类型类型收窄也就失效了。相关的类型测试可以查看 test-d/index.test-d.ts而完整的组合场景比如内联片段里嵌套数组加onUnion在 src/tests/index.test.ts 的 render inline fragment unions 测试中有详细演示。总结什么时候该用 onUnion场景推荐方案理由接口/联合类型字段需要类型收窄onUnion自动生成判别联合类型只查询单个分支的专属字段on语法更轻量标准 fragment 复用fragment适合多处复用的公共字段一句话总结如果你的 GraphQL 接口字段返回多种类型并且你希望 TypeScript 像守卫一样自动收窄类型、帮你挡住错误访问——onUnion就是那个更智能的选择。它把内联片段、判别联合、类型收窄三件事合并成一行代码让 typed-graphqlify 真正做到了无需代码生成类型依然安全。现在试着把项目里的any和手写联合类型替换成onUnion体会一把类型系统带来的安全感吧【免费下载链接】typed-graphqlifyBuild Typed GraphQL Queries in TypeScript without the code generation项目地址: https://gitcode.com/gh_mirrors/ty/typed-graphqlify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考