TypeScript重载箭头函数实现报错的排查与解决
我尝试创建一个工厂函数,它接受类型为{ item: T; weight: number }的数组,返回带有pick方法的对象。pick方法根据是否传入定义了quantity的参数,返回T或T[],类型定义如下:
type PickOneOptions = { quantity: undefined; }; type PickManyOptions = { quantity: number; }; type PickOptions = PickOneOptions | PickManyOptions; type WeightedItem<T> = { item: T; weight: number }; type WeightedTable<T> = { pick: { (): T; (options: PickOneOptions): T; (options: PickManyOptions): T[]; }; };
但在实现该工厂函数时,TypeScript报错:'T' could be instantiated with an arbitrary type which could be unrelated to 'T | T[]'。虽然使用类型断言可以消除错误,且实际调用时返回值符合预期:
const createWeightedTable = <T>(items: WeightedItem<T>[]): WeightedTable<T> => { const totalWeight = items.reduce((prev, curr) => prev + curr.weight, 0); return { pick: (options?: PickOptions) => { // 报错位置 const { quantity } = options ?? {}; const weightedItems = items; let random = Math.random() * totalWeight; if (quantity === undefined) { for (const weightedItem of weightedItems) { random -= weightedItem.weight; if (random <= 0) { return weightedItem.item; } } throw new Error(); } const result: T[] = []; for (let i = 0; i < quantity; i++) { for (const weightedItem of items) { random -= weightedItem.weight; if (random <= 0) { result.push(weightedItem.item); } } } return result; }, }; }; const table = createWeightedTable([ { item: 'a', weight: 1 }, { item: 'b', weight: 1 } ]) const resultOne = table.pick(); // string const resultMany = table.pick({ quantity: 2 }); // string[]
TypeScript无法自动将实现的pick函数返回类型与WeightedTable<T>中定义的重载签名匹配。你的实现函数被推断为返回T | T[],但重载签名要求不同参数对应明确的单一返回类型(无参数/PickOneOptions返回T,PickManyOptions返回T[])。
当泛型T被实例化为任意类型时(比如T本身就是数组类型),T | T[]会出现类型重叠,TypeScript无法保证这个联合类型能准确对应重载的返回要求,因此抛出错误。
无需类型断言,通过精确类型收窄让TypeScript明确每个分支的返回类型:
调整pick函数内的逻辑,直接针对options进行类型检查,而不是解构出quantity后再判断,这样TypeScript能将options的类型收窄到对应的PickOneOptions或PickManyOptions:
const createWeightedTable = <T>(items: WeightedItem<T>[]): WeightedTable<T> => { const totalWeight = items.reduce((prev, curr) => prev + curr.weight, 0); return { pick: (options?: PickOptions) => { let random: number; // 直接检查options,让TypeScript收窄类型 if (!options || options.quantity === undefined) { random = Math.random() * totalWeight; for (const weightedItem of items) { random -= weightedItem.weight; if (random <= 0) { return weightedItem.item; } } throw new Error("No item selected"); } else { const result: T[] = []; // 每次循环重新生成随机数,避免重复选择同一结果 for (let i = 0; i < options.quantity; i++) { random = Math.random() * totalWeight; for (const weightedItem of items) { random -= weightedItem.weight; if (random <= 0) { result.push(weightedItem.item); break; // 找到结果后跳出内层循环 } } } return result; } }, }; };
注:原批量选择逻辑存在bug(随机数仅生成一次,会导致多次选择同一结果),上述代码已顺带修复该问题。
内容的提问来源于stack exchange,提问作者Tiago

