TypeScript类型检查异常:封装NestJS Resolver装饰器报错问题
为什么TypeScript不认可逻辑看似等价的Resolver装饰器封装?
我封装了@nestjs/graphql的Resolver装饰器以添加额外功能,以下代码可正常运行:
import { Resolver as GraphQLResolver, ResolverOptions, ResolverTypeFn, } from '@nestjs/graphql'; function instanceOfResolverOptions(object: any): object is ResolverOptions { return typeof object === 'object' || 'isAbstract' in object; } export function Resolver( nameOrTypeOrOptions: | string | ResolverTypeFn // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type | Function | ResolverOptions, options?: ResolverOptions, ): ClassDecorator { return function (target) { if (nameOrTypeOrOptions instanceof Function) { GraphQLResolver(nameOrTypeOrOptions, options)(target); } else if (typeof nameOrTypeOrOptions === 'string') { GraphQLResolver(nameOrTypeOrOptions)(target); } else if (instanceOfResolverOptions(nameOrTypeOrOptions)) { GraphQLResolver(nameOrTypeOrOptions)(target); } else { GraphQLResolver()(target); } }; }
但以下写法却无法正常运行:
import { Resolver as GraphQLResolver, ResolverOptions, ResolverTypeFn, } from '@nestjs/graphql'; function instanceOfResolverOptions(object: any): object is ResolverOptions { return typeof object === 'object' || 'isAbstract' in object; } export function Resolver( nameOrTypeOrOptions: | string | ResolverTypeFn // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type | Function | ResolverOptions, options?: ResolverOptions, ): ClassDecorator { return function (target) { if (nameOrTypeOrOptions instanceof Function) { GraphQLResolver(nameOrTypeOrOptions, options)(target); } else if ( typeof nameOrTypeOrOptions === 'string' || instanceOfResolverOptions(nameOrTypeOrOptions) ) { GraphQLResolver(nameOrTypeOrOptions)(target); } else { GraphQLResolver()(target); } }; }
会触发以下TypeScript错误:
resolver.decorators.ts:27:23 - error TS2769: 没有匹配的重载调用。 最后一个重载给出如下错误。 参数类型'string | ResolverOptions'无法赋值给类型'ResolverTypeFn'。 类型'string'无法赋值给类型'ResolverTypeFn'。 27 GraphQLResolver(nameOrTypeOrOptions)(target); ~~~~~~~~~~~~~~~~~~~ node_modules/@nestjs/graphql/dist/decorators/resolver.decorator.d.ts:43:25 43 export declare function Resolver(typeFunc: ResolverTypeFn, options?: ResolverOptions): MethodDecorator & ClassDecorator; ~~~~~~~~ 最后一个重载声明于此。
这是TypeScript的正常行为,核心原因是类型收窄的局限性:
- 第一种写法中,每个
else if分支单独判断类型,TypeScript能逐个将nameOrTypeOrOptions的类型收窄到具体的string或ResolverOptions,调用GraphQLResolver时能精准匹配对应的重载签名。 - 第二种写法将
string和ResolverOptions的判断合并为||条件后,TypeScript无法在该分支内把联合类型拆分为单个具体类型,只能识别出nameOrTypeOrOptions是string | ResolverOptions的联合类型。而GraphQLResolver的重载列表中没有直接接受该联合类型的签名,TypeScript会尝试匹配最接近的重载(即接受ResolverTypeFn的那个),最终导致类型不匹配错误。
如果要保留合并分支的写法,可以通过类型断言跳过类型检查,但不推荐(会丢失类型安全性):
GraphQLResolver(nameOrTypeOrOptions as string | ResolverOptions)(target);
更稳妥的方案是保持第一种单独分支的写法,让TypeScript正确完成类型收窄。
内容的提问来源于stack exchange,提问作者GM_Alex
相关产品推荐
相关产品推荐

