TypeScript类型校验问题:如何让参数类型按顺序区分而非合并为联合类型?
问题描述
我想要创建一个可定义依赖项的函数,这些依赖会在实例化时传入函数。但目前TypeScript无法正确做类型校验,抛出以下错误:
(parameter) d1: string | number
Argument of type 'string | number' is not assignable to parameter of type 'number'.
Type 'string' is not assignable to type 'number'
怎么让TypeScript按顺序识别参数类型,而不是把它们合并成联合类型?
原代码示例
export type Injectable<T = any> = { dependencies: Injectable[]; get: () => Promise<T | null>; }; export type AsyncFactoryFn<T, Deps extends any[] = any[]> = (...args: {[K in keyof Deps]: Deps[K] extends Injectable<infer U> ? U : never}) => Promise<T>; export function injectable<T, Deps extends Injectable<any>[]>( asyncFactoryFn: AsyncFactoryFn<T, Deps>, dependencies: Deps = [] as unknown as Deps, ): Injectable<T> { return {} as Injectable<T>; } // 示例代码 const dep1 = { dependencies: [], get: () => Promise.resolve(1), } const dep2 = { dependencies: [], get: () => Promise.resolve('a'), } const injectableObject = injectable((d1, d2) => Promise.resolve(someService(d1, d2)), [dep1, dep2]); function someService(d1: number, d2: string) { console.log(d1, d2); }
解决方案
问题核心在于Deps extends Injectable<any>[]这个约束:TypeScript默认会把数组字面量推断为数组类型(比如Injectable<number>[]),而不是保留顺序的元组类型,导致参数类型被合并成联合类型。我们需要修改类型定义,引导TypeScript推断为元组,同时正确提取每个依赖的返回类型。
修改后的代码
export type Injectable<T = any> = { dependencies: Injectable[]; get: () => Promise<T | null>; }; // 用readonly元组约束Deps,确保保留每个元素的类型顺序 export type AsyncFactoryFn<T, Deps extends readonly Injectable<any>[]> = (...args: { [K in keyof Deps]: Deps[K] extends Injectable<infer U> ? U : never }) => Promise<T>; // 使用readonly元组约束,并添加泛型默认值优化推断 export function injectable<T, Deps extends readonly Injectable<any>[] = []>( asyncFactoryFn: AsyncFactoryFn<T, Deps>, dependencies: Deps, ): Injectable<T> { return {} as Injectable<T>; } // 示例代码优化 // 方式一:用as const让TypeScript保留具体类型 const dep1 = { dependencies: [], get: () => Promise.resolve(1), } as const; const dep2 = { dependencies: [], get: () => Promise.resolve('a'), } as const; // 方式二:直接指定Injectable的泛型类型(更直观) // const dep1: Injectable<number> = { // dependencies: [], // get: () => Promise.resolve(1), // }; // const dep2: Injectable<string> = { // dependencies: [], // get: () => Promise.resolve('a'), // }; const injectableObject = injectable((d1, d2) => Promise.resolve(someService(d1, d2)), [dep1, dep2]); function someService(d1: number, d2: string) { console.log(d1, d2); }
关键修改点
- 元组约束替换数组约束:把
Deps extends Injectable<any>[]改成Deps extends readonly Injectable<any>[],并设置默认值= [],强迫TypeScript将依赖列表推断为元组,保留每个元素的类型和顺序。 - 锁定依赖类型:要么给依赖对象加
as const断言,要么直接指定Injectable的泛型类型,避免TypeScript把类型拓宽为联合类型。 - 移除默认参数:原代码中
dependencies: Deps = [] as unknown as Deps的默认值会干扰元组推断,直接移除即可。
修改后,d1会被正确推断为number,d2推断为string,完全匹配someService的参数类型,类型校验顺利通过。
内容的提问来源于stack exchange,提问作者Andy Jessop
相关产品推荐
相关产品推荐

