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

标签 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 为接口(interface) 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> 

我怎样才能避免这种类型的分布,例如如何将一个类型参数绑定(bind)到 typescript 中的另一个类型参数?

我知道使用

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

无限感激,我已经为此苦苦挣扎了很长时间。

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

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

最佳答案

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

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

因为这是变量的类型,对象字面量将仅根据类型进行检查并与其兼容(type"NUM" ,与 "NUM" | "STR" 兼容,data 的类型 stringstring | 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 永远不能与 data 类型的 number 兼容,而 typeNUM 永远不能与 data 类型的 string 兼容。

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

关于typescript - 如何避免在 Typescript 中分布多个泛型类型参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54107628/

相关文章:

javascript - 如何将 getter 值绑定(bind)到 HTML 中?

typescript - 我应该在 TypeScript 中输入所有内容吗

TypeScript:在运行时访问类型信息

typescript - 用于right = | _a |的Rust泛型类型实现| b | b

typescript - 使用 typescript 的数量限制

javascript - typescript :如何在构造函数之外初始化类属性

javascript - angular2 http.post() 到本地 json 文件

typescript - 在 Protractor `await` 上使用 `element().getAttribute()` 给出错误 : "Type of ' await' operand must either be a valid promise. .."

typescript - 在 Typescript 中索引其他属性的属性

reactjs - 为什么 Typescript 多态 React 组件的事件处理程序 props 错误?