Apollo Server嵌套解析器与TypeScript对象类型不兼容问题
Apollo Server嵌套解析器的TypeScript类型兼容问题解决
问题场景
我有如下GraphQL Schema:
type Item { name: String! color: String! price: Float! } type Query { items: [Item] }
通过graphql-code-generator生成了对应的TypeScript类型(见文末)。在Apollo Server中定义了解析器:
{ Item: { price: () => 1.0, }, Query: { items: () => [ { name: 'item1', color: 'red' }, { name: 'item2', color: 'blue' }, ], }, }
实际执行查询时功能完全正常:
query { items { name color price } }
但TypeScript会抛出类型错误,提示items解析器返回的对象缺少必填的price属性。错误信息如下:
Type '() => { name: string; color: string; }[]' is not assignable to type 'Resolver<Maybe<ResolverTypeWrapper<Item>[]>, {}, ApolloContext, {}> | undefined'. Type '() => { name: string; color: string; }[]' is not assignable to type 'ResolverFn<Maybe<ResolverTypeWrapper<Item>[]>, {}, ApolloContext, {}>'. Type '{ name: string; color: string; }[]' is not assignable to type 'Maybe<ResolverTypeWrapper<Item>[]> | Promise<Maybe<ResolverTypeWrapper<Item>[]>>'. Type '{ name: string; color: string; }[]' is not assignable to type 'ResolverTypeWrapper<Item>[]'. Type '{ name: string; color: string; }' is not assignable to type 'ResolverTypeWrapper<Item>'. Property 'price' is missing in type '{ name: string; color: string; }' but required in type 'Item'. (tsserver 2322)
我不想把Schema中的price改成可空字段——实际场景中很多非空字段需要延迟解析(比如异步获取、性能优化),Schema必须保持正确的非空定义。
报错原因
生成的TypeScript类型把Item的price标记为必填,但TypeScript无法识别Apollo的嵌套解析器机制:父解析器返回的对象可以缺少子字段,这些字段会由对应类型的解析器补充。当前生成的类型直接要求父解析器返回完整的Item对象,导致类型检查不通过。
解决方案
1. 使用graphql-code-generator的typescript-resolvers插件(推荐)
默认生成的类型只描述了GraphQL的返回类型,而typescript-resolvers插件会生成专门针对解析器的类型,它会考虑嵌套解析器的存在,允许父解析器返回缺少子字段的对象。
修改codegen配置(比如codegen.ts),添加该插件:
import { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: './schema.graphql', generates: { './src/types.ts': { plugins: ['typescript', 'typescript-resolvers'], // 添加typescript-resolvers config: { useIndexSignature: true, }, }, }, }; export default config;
重新生成类型后,解析器的类型会自动兼容父解析器返回不完整对象的情况,无需手动修改解析器代码。
2. 手动定义兼容的父解析器返回类型
如果暂时不想修改codegen配置,可以手动创建一个允许缺少子字段的类型,替换解析器中的返回类型:
// 定义Item的部分类型,允许缺少price type PartialItem = Omit<Item, 'price'> & { price?: never }; const resolvers = { Item: { price: () => 1.0, }, Query: { // 断言返回类型为PartialItem数组,让TS通过检查 items: (): PartialItem[] => [ { name: 'item1', color: 'red' }, { name: 'item2', color: 'blue' }, ], }, };
3. 类型断言(应急方案)
如果只是临时解决,可以用类型断言强制让TS通过检查,但不推荐长期使用(会丢失类型检查的安全性):
const resolvers = { Item: { price: () => 1.0, }, Query: { items: () => [ { name: 'item1', color: 'red' }, { name: 'item2', color: 'blue' }, ] as unknown as Item[], // 强制断言为Item数组 }, };
生成的原始TypeScript类型
export type Maybe<T> = T | null; export type InputMaybe<T> = Maybe<T>; export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }; export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> }; export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> }; export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never }; export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: { input: string; output: string; } String: { input: string; output: string; } Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } }; export type Item = { __typename?: 'Item'; name: Scalars['String']['output']; color: Scalars['String']['output']; price: Scalars['Float']['output']; }; export type Query = { __typename?: 'Query'; items?: Maybe<Array<Maybe<Item>>>; };
内容的提问来源于stack exchange,提问作者jet
相关产品推荐
相关产品推荐

