如何实现TypeScript中createExistingProvider的参数类型匹配校验?
实现Angular InjectionToken的Existing Provider类型校验
我在Angular环境下用TypeScript实现了createValueProvider、createClassProvider等工具函数,用于确保配置Provider时的参数类型正确性。其中createValueProvider已经能正常完成类型校验,示例代码如下:
type TokenValue<Token> = Token extends InjectionToken<infer V> ? V : never; type TypeValue<Type> = Type extends new (...args: unknown[]) => infer R ? R : never; export const createValueProvider = <Token extends InjectionToken<any> | Type<any>>(provide: Token, useValue: TokenValue<Token> | TypeValue<Token>): ValueProvider => { return { provide, useValue, }; }; const MY_TOKEN = new InjectionToken<string>('My string'); createValueProvider(MY_TOKEN, 'Test'); // 正常通过 createValueProvider(MY_TOKEN, 123); // 报错:类型'number'不能分配给类型'string' interface Base { prop: string; } class BaseImpl implements Base { prop = ''; } createValueProvider(BaseImpl, { prop: '' }); // 正常通过 createValueProvider(BaseImpl, { prop: 123 }); // 报错:类型'number'不能分配给类型'string'
但createExistingProvider无法实现类似的类型校验,当前简化后的代码如下:
export const createExistingProvider = <Token extends InjectionToken<any>>( provide: Token, useExisting: Token extends InjectionToken<infer Value> ? InjectionToken<Value> : never ): Provider => { return { provide, useExisting, }; }; const EXISTING_TOKEN = new InjectionToken<string>('Existing'); const NEW_TOKEN = new InjectionToken<number>('New'); const SECOND_NEW_TOKEN = new InjectionToken<string>('Second New'); createExistingProvider(NEW_TOKEN, EXISTING_TOKEN); // 预期报错但实际不报错(一个是InjectionToken<number>,另一个是InjectionToken<string>) createExistingProvider(SECOND_NEW_TOKEN, EXISTING_TOKEN); // 正常通过
当前问题是:当第一个参数为InjectionToken<number>、第二个为InjectionToken<string>时,不会触发编译错误,需要修改实现来达成预期的类型校验。
解决方案
修改createExistingProvider的泛型定义,明确约束useExisting的Token值类型必须与provide的Token值类型完全一致,有两种可行写法:
写法一:直接将Token值类型作为泛型参数
export const createExistingProvider = <Value>( provide: InjectionToken<Value>, useExisting: InjectionToken<Value> ): Provider => { return { provide, useExisting, }; };
写法二:通过条件类型提取provide的Token值类型并约束useExisting
export const createExistingProvider = <Token extends InjectionToken<any>>( provide: Token, useExisting: InjectionToken<Token extends InjectionToken<infer V> ? V : never> ): Provider => { return { provide, useExisting, }; };
修改后的效果
使用修改后的函数后,之前的错误场景会正确触发编译错误:
const EXISTING_TOKEN = new InjectionToken<string>('Existing'); const NEW_TOKEN = new InjectionToken<number>('New'); const SECOND_NEW_TOKEN = new InjectionToken<string>('Second New'); createExistingProvider(NEW_TOKEN, EXISTING_TOKEN); // 报错:类型'InjectionToken<string>'不能分配给类型'InjectionToken<number>' createExistingProvider(SECOND_NEW_TOKEN, EXISTING_TOKEN); // 正常通过
原写法失效原因
原写法中,useExisting的类型是基于泛型参数Token的条件类型,但TypeScript在处理泛型条件类型时,不会即时将Token解析为具体的InjectionToken<Value>实例,而是保留条件类型的抽象形式,导致任何InjectionToken<any>都能匹配,因此无法触发类型不匹配的错误。修改后的写法通过明确绑定两个参数的Token值类型,确保了类型校验的有效性。
内容的提问来源于stack exchange,提问作者Mick412
相关产品推荐
相关产品推荐

