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

如何编写类型守卫区分Angular的Type<T>与普通函数类型?

问题:如何编写类型守卫区分Angular的Type与函数类型

我定义了一个支持多种对象提供方式的类型:

type ObjSource<T> = InjectionToken<T> | Type<T> | ((context: IContext) => (T | Promise<T>)) | Promise<T> | T;

其中IContext是自定义接口,用于提供应用上下文信息。

为正确处理这些类型,我需要通过自定义类型守卫判断对象源类型,处理逻辑如下:

  • 若为InjectionToken<T>或Type<T>,传入Angular的inject函数交由依赖注入处理;
  • 若为((context: IContext) => (T | Promise<T>))类型,执行函数并将结果传入Promise.resolve;
  • 若为Promise<T>或T类型,直接传入Promise.resolve。

我已实现识别InjectionToken<T>的类型守卫,但无法区分Type<T>与上述函数类型。Angular的Type<T>定义为:

class Type<T> extends Function {
  constructor(...args: any[]): T
}

在示例应用中尝试多种isType<T>实现均失败,现寻求如何编写类型守卫区分二者?


解决方案

要区分Angular的Type<T>(类构造函数类型)和普通业务函数,核心是利用类构造函数的原型特征——Angular的Type<T>本质是可通过new实例化的类,而业务函数是接收IContext的普通调用函数,二者在原型结构上有明显差异。

实现思路

  1. 先通过已实现的类型守卫排除InjectionToken;
  2. 对函数类型的候选值,判断是否具备类构造函数的特征:
    • 类的prototype属性会指向自身的原型对象,且原型上的constructor会指向类本身;
    • 若处理的是Angular编译后的类,还可利用框架自动添加的ngMetadataName属性做精准判断。

具体代码实现

import { Type, InjectionToken, inject } from '@angular/core';
import { IContext } from './your-context-path'; // 替换为你的上下文接口路径

// 已实现的InjectionToken类型守卫
function isInjectionToken<T>(source: unknown): source is InjectionToken<T> {
  return typeof source === 'object' && source !== null && '__brand__' in source && source.__brand__ === 'InjectionToken';
}

// 区分Type<T>的类型守卫
function isType<T>(source: unknown): source is Type<T> {
  if (typeof source !== 'function') return false;
  if (isInjectionToken(source)) return false;

  const targetFn = source as Function;
  // 方案1:基于原型特征判断(通用类判断逻辑)
  return targetFn.prototype !== undefined && targetFn.prototype.constructor === targetFn;

  // 方案2:针对Angular编译类的精准判断(推荐处理Angular服务/组件类时使用)
  // return 'ngMetadataName' in targetFn;
}

// 区分业务函数的类型守卫
function isContextFunction<T>(source: unknown): source is (context: IContext) => T | Promise<T> {
  return typeof source === 'function' && !isType(source) && !isInjectionToken(source);
}

// 处理ObjSource的核心函数
async function resolveObjSource<T>(source: ObjSource<T>, context: IContext): Promise<T> {
  if (isInjectionToken(source) || isType(source)) {
    return inject(source);
  } else if (isContextFunction(source)) {
    return Promise.resolve(source(context));
  } else {
    return Promise.resolve(source as T);
  }
}

补充说明

  • 若你的业务函数使用箭头函数定义,箭头函数没有prototype属性,此时可以直接在isContextFunction中加入source.prototype === undefined的判断,进一步缩小范围;
  • 方案2的ngMetadataName是Angular编译时为组件、指令、服务等类添加的内部标识,仅适用于Angular框架生成的类,判断精度更高;
  • 类型守卫的判断顺序很重要:必须先排除InjectionToken,再区分Type<T>和普通函数,避免逻辑混淆。

内容的提问来源于stack exchange,提问作者F-H

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 17:25:32