如何避免在Typescript中分布多个泛型类型参数?

时间:2019-01-09 10:09:16

标签: typescript typescript-generics

我不知道如何正确地表达我的问题,所以我举一个例子。

type ValueType = "NUM" | "STR";

type TypeOf<T>
    = T extends "NUM" ? number
    : T extends "STR" ? string
    : never;

interface TypedValue<T = ValueType> {
    type: T;
    data: TypeOf<T>;
}


// Compiles, as intended
const test1: TypedValue = { type: "NUM", data: 123 };

// Does not compile, as intended
const test2: TypedValue<"NUM"> = { type: "NUM", data: "123" };

// Should not compile, but does...
const test3: TypedValue = { type: "NUM", data: "123" };

看来Typescript为接口TypedValue生成了许多具体类型:

如此

interface TypedValue<T = ValueType, D = TypeOf<T>> 

对应于

interface TypedValue<"NUM", number> 
interface TypedValue<"NUM", string> 
interface TypedValue<"NUM", never> 
interface TypedValue<"STR", number> 
interface TypedValue<"STR", string> 
interface TypedValue<"STR", never> 

甚至更多,而我实际上希望这种通用类型只对应于

interface TypedValue<"NUM", number> 
interface TypedValue<"STR", string> 

如何避免这种类型的分布,例如如何在打字稿中将一个类型参数绑定到另一个类型参数?

我知道抑制使用类型分布的技巧

type TypeOf<T>
    = [T] extends ["NUM"] ? number
    : [T] extends ["STR"] ? string
    : never;

但是我似乎无法自己解决难题,我真的想对这个神奇的类型系统进行更深入的研究,因此欢迎您提供任何帮助:)可以肯定的是jcalz知道如何解决这个问题;)

编辑 在Titian Cernicova-Dragomir回答后,它终于点击了!我个人使用以下代码片段更好地理解了该解决方案:

type Pairs1<T> = [T, T];
type Pairs2<T> = T extends (infer X) ? [X, X] : never;

type P1 = Pairs1<"A" | "B">; // => ["A" | "B", "A" | "B"]  
type P2 = Pairs2<"A" | "B">; // => ["A", "A"] | ["B" | "B"]

似乎发生了什么,Typescript编译器将为每个联合成员T extends (infer X)检查"A"|"B",该联合成员总是会成功,但是现在它将匹配的类型变量绑定到非联合 strong>类型变量X。实际上,infer X并不是必需的,但是它有助于我更好地理解它。

无限感激,我为此一直苦苦挣扎。

所以现在我终于理解了Typescript手册的以下摘录:

在分布式条件类型T extends U ? X : Y的实例中,条件类型中对T的引用被解析为联合类型的个个体(即{{ 1}}是指条件类型在联合类型上分布后的各个组成部分。此外,对TT的引用还具有附加的类型参数约束X(即,U被认为可分配给T中的U)。

1 个答案:

答案 0 :(得分:2)

问题不仅仅在于解决方案的条件部分,而更多的是变量类型注释与默认类型参数一起工作的方式。

如果未为变量指定类型,则将推断其类型。如果指定类型,则不会进行推断。因此,当您说const test3: TypedValue时,不会对通用类型参数进行推断,并且将使用类型参数的默认值。因此const test3: TypedValue等效于const test3: TypedValue<"NUM" | "STR">,等效于const test3: { type: "NUM" | "STR"; data: number | string; }

由于这是变量的类型,因此将仅根据该类型检查对象文字并与之兼容(type"NUM",与"NUM" | "STR",{ {1}}的类型为data,与string兼容)

您可以使用分布行为或条件类型将类型完全转换为真正的有区别的联合:

string | number

type ValueType = "NUM" | "STR"; type TypeOf<T> = T extends "NUM" ? number : T extends "STR" ? string : never; type TypedValue<T = ValueType> = T extends any ? { type: T; data: TypeOf<T>; }: never; // Compiles, as intended const test1: TypedValue = { type: "NUM", data: 123 }; // Does not compile, as intended const test2: TypedValue<"NUM"> = { type: "NUM", data: "123" }; // does not compile now const test3: TypedValue = { type: "NUM", data: "123" }; 以上的定义中,没有类型参数的等效于:

TypedValue

这意味着{ type: "NUM"; data: number; } | { type: "STR"; data: string; } type永远不能与类型STR的{​​{1}}兼容,而datanumber可以兼容永远与type类型的NUM不兼容。

data中的条件类型不用于表示实际条件,每个string都将扩展TypedValue。条件类型的重点是分布在T上。这意味着,如果any是一个联合,则结果将是应用于联合的每个成员的类型T。详细了解条件类型here

的分布行为