You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的正常行为,核心原因是类型收窄的局限性:

  1. 第一种写法中,每个else if分支单独判断类型,TypeScript能逐个将nameOrTypeOrOptions的类型收窄到具体的string或ResolverOptions,调用GraphQLResolver时能精准匹配对应的重载签名。
  2. 第二种写法将string和ResolverOptions的判断合并为||条件后,TypeScript无法在该分支内把联合类型拆分为单个具体类型,只能识别出nameOrTypeOrOptions是string | ResolverOptions的联合类型。而GraphQLResolver的重载列表中没有直接接受该联合类型的签名,TypeScript会尝试匹配最接近的重载(即接受ResolverTypeFn的那个),最终导致类型不匹配错误。

如果要保留合并分支的写法,可以通过类型断言跳过类型检查,但不推荐(会丢失类型安全性):

GraphQLResolver(nameOrTypeOrOptions as string | ResolverOptions)(target);

更稳妥的方案是保持第一种单独分支的写法,让TypeScript正确完成类型收窄。

内容的提问来源于stack exchange,提问作者GM_Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:53:10