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

Posted

技术标签:

【中文标题】如何避免在 Typescript 中分布多个泛型类型参数?【英文标题】:How to avoid distribution over multiple generic type parameters in Typescript? 【发布时间】:2019-06-04 01:55:11 【问题描述】:

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

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 回答后终于点击了!我个人使用以下代码 sn-p 更好地理解解决方案:

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 编译器将为每个联合成员 "A"|"B" 检查 T extends (infer X),这总是成功,但它现在将匹配的类型变量绑定到 非联合 类型变量Xinfer X 实际上是不需要的,但它帮助我更好地理解它。

无限感激,我为此苦苦挣扎了很久。

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

在分配条件类型T extends U ? X : Y 的实例化中,条件类型中对T 的引用被解析为联合类型的各个组成部分(即T 指的是条件类型后的单个成分分布在联合类型上)。此外,在X 中对T 的引用有一个额外的类型参数约束U(即T 被认为可以分配给X 中的U)。

【问题讨论】:

jcalz 可能还在睡觉,你必须接受我的回答 :) 防止联合类型分布的简单通用解决方案是通过接口lorefnon.tech/2019/05/02/…将它们装箱 【参考方案1】:

问题不在于解决方案的条件部分,而在于变量类型注释与默认类型参数相结合的工作方式。

如果没有为变量指定类型,则将推断其类型。如果指定类型,则不会发生推理。因此,当您说const test3: TypedValue 时,不会对泛型类型参数进行推断,并且将使用类型参数的默认值。所以const test3: TypedValue 等价于const test3: TypedValue&lt;"NUM" | "STR"&gt; 等价于const test3: type: "NUM" | "STR"; data: number | string;

由于这是变量的类型,对象字面量将仅根据类型进行检查并且它与它兼容(type"NUM",与 "NUM" | "STR" 兼容,data 是 @ 类型987654330@兼容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;

这意味着typeSTR 永远不能与number 类型的data 兼容,而typeNUM 永远不能与@987654342 类型的data 兼容@。

TypedValue 中的条件类型不用于表达实际条件,每个T 都会扩展any。条件类型的要点是分发T。这意味着如果T 是一个联合,则结果将是应用于联合的每个成员的 type: T; data: TypeOf&lt;T&gt;; 类型。阅读更多关于条件类型的分配行为here

【讨论】:

太棒了,非常感谢!但是,我不明白这是如何工作的,因为每种类型 extends any 都没有?那么为什么条件过滤任何东西呢?您能否给我一些参考资料以便更好地理解这一点? @Ziriax 在最后添加了一个解释,我们只是使用了条件类型的分配部分,我们实际上并不关心条件。

以上是关于如何避免在 Typescript 中分布多个泛型类型参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 typescript 中为泛型类编写扩展作为 getter

在 Typescript 的泛型类中初始化泛型类型

将泛型值推送到 Typescript 中泛型类中的泛型列表

TypeScript创建泛型类报错(泛型类)

TypeScript创建泛型类报错(泛型类)

TypeScript创建泛型类报错(泛型类)