如何编写类型守卫区分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的普通调用函数,二者在原型结构上有明显差异。
实现思路
- 先通过已实现的类型守卫排除
InjectionToken; - 对函数类型的候选值,判断是否具备类构造函数的特征:
- 类的
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
相关产品推荐
相关产品推荐

