TypeScript工厂函数返回类型推断异常:期望精准类型而非联合类型
解决TypeScript工厂函数返回类型精准推断问题
问题背景
编写的工厂函数接收FactoryOptions<T>类型参数,其中可选的items字段支持两种类型:Item[]数组,或接收默认数组返回新数组的函数(defaultItems: Item[]) => Item[]。但当前函数返回类型总是推断为联合类型,无法根据不同使用场景返回精准类型。
原代码及问题示例:
type Item<T = string> = { value: T }; type DefaultItems = Item<number>[]; type AnyItems = Item<any>[]; type FactoryOptions<T extends AnyItems = DefaultItems> = { items?: T | ((defaultItems: DefaultItems) => T); }; const defaultItems: DefaultItems = [{ value: 1 }, { value: 2 }]; const foo: Item<string> = { value: "foo" }; const num: Item<number> = { value: 3 }; function factory<T extends AnyItems = DefaultItems>(options: FactoryOptions<T> = {}) { const { items: itemsOrFunc = defaultItems } = options; return typeof itemsOrFunc === 'function' ? itemsOrFunc(defaultItems) : itemsOrFunc; } // 返回 DefaultItems(符合预期) const test1 = factory(); // 实际返回:DefaultItems | Item<string>[],期望:Item<string>[] const test2 = factory({ items: [foo] }) // 实际返回:DefaultItems | Item<number>[],期望:Item<number>[] const test3 = factory({ items: () => [num] })
解决方案
以下两种方法均可实现每种场景下返回类型的精准推断:
方法一:泛型条件类型推导
通过泛型捕获传入参数的具体类型,结合条件类型判断items是数组还是函数,从而推导精准的返回类型:
function factory<O extends Partial<{ items: AnyItems | ((defaultItems: DefaultItems) => AnyItems) }> = {}>( options: O = {} as O ): O extends { items: infer I } ? I extends ((defaultItems: DefaultItems) => infer R) ? R : I : DefaultItems { const { items: itemsOrFunc = defaultItems } = options; return typeof itemsOrFunc === 'function' ? itemsOrFunc(defaultItems) : itemsOrFunc as any; }
类型推导逻辑:
- 若未传入
items,返回默认的DefaultItems - 若传入
items为数组,直接返回该数组的类型 - 若传入
items为函数,返回函数的返回值类型
方法二:函数重载
为三种使用场景分别定义重载签名,让TypeScript根据参数匹配对应的返回类型:
// 重载1:无参数,返回默认数组类型 function factory(): DefaultItems; // 重载2:传入自定义items数组,返回该数组的类型 function factory<T extends AnyItems>(options: { items: T }): T; // 重载3:传入生成函数,返回函数的返回值类型 function factory<T extends AnyItems>(options: { items: (defaultItems: DefaultItems) => T }): T; // 函数实现(类型可宽松处理) function factory(options: any = {}) { const { items: itemsOrFunc = defaultItems } = options; return typeof itemsOrFunc === 'function' ? itemsOrFunc(defaultItems) : itemsOrFunc; }
优势:每个场景对应明确的签名,可读性更强,适合需要清晰区分输入输出的场景。
测试验证
两种方法均可实现预期的类型推断:
const test1 = factory()→ 类型为DefaultItemsconst test2 = factory({ items: [foo] })→ 类型为Item<string>[]const test3 = factory({ items: () => [num] })→ 类型为Item<number>[]
内容的提问来源于stack exchange,提问作者madcapnmckay
相关产品推荐
相关产品推荐

