如何用TypeScript泛型包装相似查询类型并解决类型兼容问题
有多个返回相似结构的GraphQL查询类型(如QueryA、QueryB),希望通过泛型类型统一包装这些查询,避免重复定义结构。
基础查询类型如下:
type QueryA = { __typename?: 'Query'; productsConnection: { __typename?: 'ProductConnection'; edges: Array<{ __typename?: 'ProductEdge'; cursor: string; node: Product }>; pageInfo: PageInfo; aggregate: Aggregate; } } type QueryB = { __typename?: 'Query'; productVariantConnection: { __typename?: 'productVariantConnection'; edges: Array<{ __typename?: 'productVariantEdge'; cursor: string; node: ProductVariant }>; pageInfo: PageInfo; aggregate: Aggregate; } }
尝试定义泛型类型统一处理:
type Edge<T> = { cursor: string; node: T; } type CursorQuery<T> = { [key: string]: { edges: Array<Edge<T>>, pageInfo: PageInfo, aggregate: Aggregate } };
测试调用时:
const test = (await sdk.GetProductCursor()) as CursorQuery<Product>
出现以下类型错误:
Type 'QueryA' is not compatible with type 'CursorQuery'.
Property '__typename' is incompatible with index signature.
Type 'string' is incompatible with type '{ edges: Edge[]; pageInfo: PageInfo; aggregate: Aggregate; }'
即使扩展类型包含__typename,错误依然存在:
type Edge<T> = { cursor: string; node: T; } type CursorHeader = { __typename?: 'Query'; } type CursorQuery<T> = CursorHeader & { [key: string]: { edges: Array<Edge<T>>, pageInfo: PageInfo, aggregate: Aggregate } };
问题根源
TypeScript的索引签名要求对象中所有字符串键的属性类型都必须匹配索引定义的类型。__typename作为字符串键,类型是'Query' | undefined,和索引签名要求的Connection结构类型不兼容,因此即使__typename是可选的,也会触发类型冲突。
正确的泛型定义方式
我们可以拆分通用结构,通过泛型指定Connection字段的名称,避免使用全局索引签名:
- 先定义通用的
Edge和Connection类型(保留可选的__typename以匹配原结构):
type Edge<T> = { __typename?: string; cursor: string; node: T; }; type Connection<T> = { __typename?: string; edges: Array<Edge<T>>; pageInfo: PageInfo; aggregate: Aggregate; };
- 定义泛型
CursorQuery,通过第一个泛型参数指定Connection字段的名称:
type CursorQuery<ConnKey extends string, T> = { __typename?: 'Query'; } & Record<ConnKey, Connection<T>>;
使用示例
针对QueryA和QueryB,可以这样使用泛型:
// 匹配QueryA的类型 const productQuery = (await sdk.GetProductCursor()) as CursorQuery<'productsConnection', Product>; // 匹配QueryB的类型 const variantQuery = (await sdk.GetProductVariantCursor()) as CursorQuery<'productVariantConnection', ProductVariant>;
更灵活的替代方案
如果不需要明确指定Connection字段名称,仅需确保对象包含至少一个Connection类型的字段+可选__typename,可以使用以下定义:
type CursorQuery<T> = { __typename?: 'Query'; } & { [K: string]: Connection<T>; };
不过这种方式类型检查精度稍低,更推荐第一种指定字段名称的方式。
内容的提问来源于stack exchange,提问作者Benjamin Fourgeaud

