TypeScript DI框架依赖参数类型推断问题求助
实现TypeScript DI框架的依赖参数类型自动推断
要解决根据deps键自动推断factory依赖参数类型的问题,我们需要通过TypeScript的条件类型、映射类型和递归泛型,将deps数组的每个键与对应的服务/模块服务类型绑定,并约束factory的参数元组与deps的顺序、类型完全匹配。
核心类型工具解析
ExtractModuleServices:提取模块factory返回的服务对象类型
type ExtractModuleServices<T extends () => Promise<(...args: any[]) => ModuleReturn>> = NonNullable<ReturnType<Awaited<ReturnType<T>>>>['services'];ResolveDep:解析单个依赖键对应的实际类型,区分全局服务(如
cache)和模块服务(如data.createResource)type ResolveDep< Modules extends Record<string, { factory: () => Promise<(...args: any[]) => ModuleReturn> }>, Services extends Record<string, { factory: () => Promise<(...args: any[]) => any> }>, Dep extends string > = Dep extends `${infer ModName}.${infer ServName}` ? ModName extends keyof Modules ? ServName extends keyof ExtractModuleServices<Modules[ModName]['factory']> ? ExtractModuleServices<Modules[ModName]['factory']>[ServName] : never : never : Dep extends keyof Services ? ReturnType<Awaited<ReturnType<Services[Dep]['factory']>>> : never;DepsToParams:将
deps数组转换为对应的参数类型元组,保证顺序一致type DepsToParams< Modules extends Record<string, any>, Services extends Record<string, any>, Deps extends string[] > = { [K in keyof Deps]: ResolveDep<Modules, Services, Deps[K]> };AllowedDeps:约束
deps的有效值,只能是已定义的全局服务或模块服务键type AllowedDeps< Modules extends Record<string, { factory: () => Promise<(...args: any[]) => ModuleReturn> }>, Services extends Record<string, any> > = | (keyof Services & string) | { [K in keyof Modules & string]: `${K}.${ExtractModuleServiceKeys<Modules[K]['factory']>}` }[keyof Modules & string];
完整实现代码
type ModuleReturn = { services?: Record<string, any>; }; // 提取模块factory返回的服务类型 type ExtractModuleServices<T extends () => Promise<(...args: any[]) => ModuleReturn>> = NonNullable<ReturnType<Awaited<ReturnType<T>>>>['services']; // 提取模块的服务键名 type ExtractModuleServiceKeys<T extends () => Promise<(...args: any[]) => ModuleReturn>> = keyof ExtractModuleServices<T> & string; // 解析单个依赖对应的类型 type ResolveDep< Modules extends Record<string, { factory: () => Promise<(...args: any[]) => ModuleReturn> }>, Services extends Record<string, { factory: () => Promise<(...args: any[]) => any> }>, Dep extends string > = // 处理模块服务,如 "data.createResource" Dep extends `${infer ModName}.${infer ServName}` ? ModName extends keyof Modules ? ServName extends keyof ExtractModuleServices<Modules[ModName]['factory']> ? ExtractModuleServices<Modules[ModName]['factory']>[ServName] : never : never // 处理全局服务,如 "cache" : Dep extends keyof Services ? ReturnType<Awaited<ReturnType<Services[Dep]['factory']>>> : never; // 将deps数组转换为对应的参数类型元组 type DepsToParams< Modules extends Record<string, any>, Services extends Record<string, any>, Deps extends string[] > = { [K in keyof Deps]: ResolveDep<Modules, Services, Deps[K]> }; // 单个模块的配置类型,关联deps和factory参数 type ModuleDefinition< Modules extends Record<string, any>, Services extends Record<string, any>, Deps extends string[] = [] > = { deps?: Deps; enabled?: (state: any) => boolean; factory: () => Promise<(...args: DepsToParams<Modules, Services, Deps>) => ModuleReturn>; }; // 单个服务的配置类型,关联deps和factory参数 type ServiceDefinition< Modules extends Record<string, any>, Services extends Record<string, any>, Deps extends string[] = [] > = { deps?: Deps; factory: () => Promise<(...args: DepsToParams<Modules, Services, Deps>) => any>; }; // 约束deps的有效值:只能是全局服务或模块服务 type AllowedDeps< Modules extends Record<string, { factory: () => Promise<(...args: any[]) => ModuleReturn> }>, Services extends Record<string, any> > = | (keyof Services & string) | { [K in keyof Modules & string]: `${K}.${ExtractModuleServiceKeys<Modules[K]['factory']>}` }[keyof Modules & string]; // 最终的App配置类型,确保每个模块/服务的deps都是有效值,且factory参数匹配deps类型 type AppConfig< Modules extends Record<string, ModuleDefinition<Modules, Services>>, Services extends Record<string, ServiceDefinition<Modules, Services>> > = { modules: { [K in keyof Modules]: Omit<Modules[K], 'deps'> & { deps?: AllowedDeps<Modules, Services>[]; }; }; services: { [K in keyof Services]: Omit<Services[K], 'deps'> & { deps?: Exclude<AllowedDeps<Modules, Services>, K>[]; }; }; }; // 实现createApp函数 export async function createApp< Modules extends Record<string, ModuleDefinition<Modules, Services>>, Services extends Record<string, ServiceDefinition<Modules, Services>> >(config: AppConfig<Modules, Services>) {}
测试示例
const app = await createApp({ modules: { data: { factory: () => Promise.resolve(() => ({ services: { createResource: () => {} } })) }, users: { deps: ['data.createResource'], factory: () => Promise.resolve((createResource: () => void) => ({})), // ✅ 无报错:参数类型匹配deps }, users2: { deps: ['data.createResource'], factory: () => Promise.resolve(() => ({})), // ❌ 报错:缺少对应依赖参数 }, users3: { deps: ['cache'], factory: () => Promise.resolve(() => ({})), // ❌ 报错:缺少对应依赖参数 }, users4: { deps: ['cache', 'data.createResource'], factory: () => Promise.resolve(() => ({})), // ❌ 报错:缺少两个依赖参数 }, users5: { deps: ['cache', 'data.createResource'], factory: () => Promise.resolve((cache: {}, createResource: () => void) => ({})), // ✅ 无报错:参数顺序、类型匹配 }, }, services: { cache: { factory: () => Promise.resolve(() => ({})) }, }, });
效果说明
- 当
factory的参数元组与deps数组对应的类型、顺序完全一致时,TypeScript不会报错; - 当参数缺失、顺序错误或类型不匹配时,TypeScript会立即抛出类型错误,符合预期的校验逻辑。
内容的提问来源于stack exchange,提问作者Andy Jessop
相关产品推荐
相关产品推荐

