解决TypeScript类型守卫兼容GraphQL允许枚举值的问题
问题根源
原isType的泛型约束T extends GraphQLTypename<O>要求typename必须是目标对象__typename的字面量类型(比如'Article'),但枚举值的类型是枚举本身(比如ContentTypes.Article),TypeScript无法自动将枚举类型与字面量类型做兼容匹配,导致类型检查失败。
你封装的isContentType中,T & ContentTypes的泛型推断逻辑存在问题:T是从O['__typename']推导的字面量类型,ContentTypes是枚举类型,两者的交集在TypeScript泛型推断阶段无法正确关联,最终返回never类型。
解决方案
方案1:修改原isType,放宽类型约束以支持枚举
调整isType的泛型定义,允许typename接受字符串/数字类型(覆盖枚举的类型范围),同时保留Extract的类型过滤逻辑:
type GraphQLObject = { __typename?: string }; export const isType = <O extends GraphQLObject, T extends string | number>( obj: O, typename: T ): obj is Extract<O, { __typename: T }> => obj.__typename === typename;
原理:枚举值的实际值与对应的字面量字符串(如ContentTypes.Article的值是'Article')在结构类型上兼容,TypeScript会自动识别Extract<O, { __typename: T }>中的T对应的字面量分支,从而正确缩小类型。
方案2:针对枚举创建专用守卫
如果需要严格绑定枚举与目标类型的关系,可以专门定义枚举对应的守卫,明确关联枚举值和字面量类型:
enum ContentTypes { Article = 'Article', Post = 'Post', } // 提取枚举的字面量类型 type ContentTypeLiteral = `${ContentTypes}`; type GraphQLObject = { __typename?: string }; export const isContentType = <O extends GraphQLObject>( obj: O, typename: ContentTypes ): obj is Extract<O, { __typename: ContentTypeLiteral }> => obj.__typename === typename;
原理:通过模板字面量类型${ContentTypes}将枚举类型转换为对应的字符串字面量集合('Article' | 'Post'),让Extract能准确匹配目标对象的__typename分支。
方案3:精准绑定枚举与目标联合类型(针对特定场景)
如果你的目标类型(如Node)是固定的,可以直接关联枚举和目标类型的分支,进一步提升类型安全性:
enum ContentTypes { Article = 'Article', Post = 'Post', } type Node = | { __typename: 'Article', properties: { name: string } } | { __typename: 'Person', properties: { email: string } }; // 只保留枚举中存在的Node分支 type ContentNode = Extract<Node, { __typename: `${ContentTypes}` }>; export const isContentType = ( obj: Node, typename: ContentTypes ): obj is Extract<ContentNode, { __typename: typeof typename }> => obj.__typename === typename;
原理:先过滤出Node中属于枚举范围的分支,再通过typeof typename匹配具体的枚举值对应的字面量类型,确保守卫只处理合法的内容类型。
验证示例
修改后的守卫可以正常使用枚举值,且类型推断正确:
const data: { node: Node } = { node: { __typename: 'Article', properties: { name: 'Foo' } } }; // 方案1:使用修改后的isType isType(data.node, ContentTypes.Article); data.node.properties.name; // 无类型错误,可正常访问 // 方案2/3:使用isContentType isContentType(data.node, ContentTypes.Article); data.node.properties.name; // 无类型错误,可正常访问
内容的提问来源于stack exchange,提问作者robinvdvleuten

