使用union-typed参数调用函数时TypeScript错误

8yoxcaq7  于 2023-04-22  发布在  TypeScript
关注(0)|答案(1)|浏览(76)

使用TypeScript,我定义了WeightedItemcreateWeightedTable如下:

type WeightedItem<T> = { item: T; weight: number };

const createWeightedTable = <T>(items: WeightedItem<T>[] | T[], options?: Options): WeightedTable<T>

我惊讶地发现这一点:

const weightedItems = [{ item: "a" }];

createWeightedTable(weightedItems)

使TypeScript抱怨以下内容:

Argument of type '{ item: string; }[]' is not assignable to parameter of type 'WeightedItem<string>[] | string[]'.
  Type '{ item: string; }[]' is not assignable to type 'WeightedItem<string>[]'.
    Property 'weight' is missing in type '{ item: string; }' but required in type 'WeightedItem<string>'.ts(2345)

然而,这些变化不会:

const weightedItems = [{ item: "a", foo: "bar" }];

createWeightedTable(weightedItems);
const weightedItems = [{ weigtht: 1 }];

createWeightedTable(weightedItems);

为什么会这样呢?
为什么TypeScript假设{ item: "a" }是一个“有缺陷的”WeightedItem,而不是另一个通用的T,但对于{ item: "a", foo: "bar" }{ weight: 1 }却不是这样?

编辑:

根据建议,在此处添加了最小可重现示例。

tvmytwxo

tvmytwxo1#

看起来这被认为是TypeScript中的一个bug,正如microsoft/TypeScript#29471中所报告的那样,尽管它已经打开了很长一段时间,并且没有迹象表明它会很快得到解决。
TypeScript的推理算法使用各种启发式方法,例如将“优先级”级别分配给generic * 推理站点 ,可以进行推理的地方,然后使用最高优先级站点来提出推理的候选者。如果该候选者不起作用,编译器通常只是报告错误而不是退回到较低优先级的推理站点。推理算法决不是完美的,并且不是microsoft/TypeScript#30134中描述的正式正确的 * 完整unification 算法,但它在各种真实的世界的代码中表现良好,因此更改它来解决问题可能弊大于利。我的意思是:最好还是假设这不会改变
无论如何,在WeightedItem<T>[] | T[]中,类型参数T有两个推断点,就像WeightedItem<**T₁**>[] | **T₂**[]一样。看起来编译器给T₁分配了比T₂更高的优先级,所有的事情都是这样。你可以通过交换联合成员的顺序来改变这种行为,就像T[] | WeightedItem<T>[]一样:

type WeightedItem<T> = { item: T; weight: number };
declare const createWeightedTable: <T>(
    items: T[] | WeightedItem<T>[]) => WeightedTable<T>;

const weightedItems = [{ item: "a" }];
const r1 = createWeightedTable(weightedItems);
// const ret: WeightedTable<{ item: string; }> 👍

不幸的是,这将阻止第二个union成员被匹配,因为任何数组都将匹配T[],包括那些匹配WeightedItem<T>[]的数组:

const w: WeightedItem<string> = { item: "b", weight: 123 }
const r2 = createWeightedTable([w])
// const r2: WeightedTable<WeightedItem<string>> 👎

如果你想解决这个问题,你可能需要更明确地指导类型,也许可以使用conditional type

type UnwrapWeightedItem<T> = T extends WeightedItem<infer U> ? U : T
declare const createWeightedTable: <T>(
    items: T[]) => WeightedTable<UnwrapWeightedItem<T>>;

const weightedItems = [{ item: "a" }];
const r1 = createWeightedTable(weightedItems);
// const ret: WeightedTable<{ item: string; }> 👍

const w: WeightedItem<string> = { item: "b", weight: 123 }
const r2 = createWeightedTable([w])
// const r2: WeightedTable<string> 👍

这是可行的,因为我们只是让T被推断为数组元素类型,然后用条件类型UnwrapWeightedItem<T>计算WeightedTable<>的类型参数。这并不理想,特别是在函数实现中,编译器更难理解泛型条件类型,但至少你可以从调用方计算类型。
Playground链接到代码

相关问题