代码之家  ›  专栏  ›  技术社区  ›  Ziriax

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

  •  3
  • Ziriax  · 技术社区  · 7 年前

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

    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> 
    

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

    我知道使用

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

    但我自己似乎无法解决这个难题,我真的想在这个神奇的系统中深入挖掘,所以欢迎任何帮助:)很肯定JCALZ知道如何解决这个问题;)

    编辑 在提香·塞尔尼科娃·德拉戈米尔回答后,它终于发出了滴答声!我个人通过以下代码片段更好地理解解决方案:

    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"]
    

    如果出现这种情况,类型脚本编译器将检查 T extends (infer X) 对于每个工会成员 "A"|"B" ,但它现在将匹配的类型变量绑定到 不愈合 类型变量 X . 以及 infer X 实际上并不需要,但它帮助我更好地理解它。

    无限感激,我已经为此奋斗了很长时间。

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

    在分布式条件类型的实例化中 T extends U ? X : Y ,参考 T 在条件类型中,解析为 联合型的个别成分 (即 T 指条件类型分布在联合类型上之后的单个成分)。此外,参考 T 在内部 X 具有附加的类型参数约束 U (即 T 被认为可转让给 U 在内部 X )

    1 回复  |  直到 7 年前
        1
  •  2
  •   Titian Cernicova-Dragomir    7 年前

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

    如果没有为变量指定类型,则将推断其类型。如果指定类型,则不会发生推理。所以当你说 const test3: TypedValue ,不会对泛型类型参数进行推理,将使用类型参数的默认值。所以 常量测试3:typedValue 等于 const test3: TypedValue<"NUM" | "STR"> 相当于 const test3: { type: "NUM" | "STR"; data: number | string; }

    因为这是变量的类型,所以对象文本只会与该类型进行检查,并且它与该类型兼容。( type 是 "NUM" ,兼容 "NUM" | "STR" , 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;
    }
    

    这意味着 类型 到 STR 永远无法与 数据 类型的 number 和A 类型 到 NUM 永远无法与 数据 类型的 一串 .

    条件类型 附加值 不用于表示实际情况,每 T 将延伸 any . 条件类型的要点是在 T . 这意味着如果 T 是联合,结果将是类型 { type: T; data: TypeOf<T>; } 适用于工会的每一个成员。阅读更多关于条件类型的分布行为的信息 here