• [Typescript] Contravariant type positions


    Co-Variance:

    declare let b: string
    declare let c: string | number
    
    c = b // ✅
    
    // string is a sub-type of string | number
    // all elements of string appear in string | number
    // So we can assign b to c
    // c still behaves as we originally intended it

    Contra-variance:

    But this doesn' work with function params:

    type Fun<T> = (...args: T[]) => void
    
    declare let f: Fun<string>
    declare let g: Fun<string | number>
        
    g = f //  this cannot be assigned

    Interesting... when we have just stringstring | number, it works

    But when we wrap with function Fun<string>Fun<string | number>, it doesn't work

    It is important to remember that you can’t assign a sub-type to a super-type when dealing with function arguments

    If you think about, when we assign fto g, we suddenly can't call gwith numbersanymore.

    We miss the part of the contract of g

    This is contra-variance, and it effectively works like an intersection.

    This is what happens when we put contra-variant positions in a conditional type:

    type UnionToIntersection<T> = 
      (T extends any ? (x: T) => any : never) extends 
      (x: infer R) => any ? R : never

    Ris the contra-variant position.

    TypeScript creates an intersection out of it. SO Rwill be the intersection of union T.

    Meaning that since we infer from a function argument, TypeScript knows that we have to fulfill the complete contract. Creating an intersection of all constituents in the union.

    Basically, union to intersection.

    Let’s run it through.

    type UnionToIntersection<T> = 
      (T extends any ? (x: T) => any : never) extends 
      (x: infer R) => any ? R : never
    
    type Format320 = { urls: { format320p: string } }
    type Format480 = { urls: { format480p: string } }
    type Format720 = { urls: { format720p: string } }
    type Format1080 = { urls: { format1080p: string } }
    
    type Video = BasicVideoData & (
      Format320 | Format480 | Format720 | Format1080
    )
    
    type Intersected = UnionToIntersection<Video["urls"]>
    
    // equals to
    
    // unwrap Video["urls"]
    type Intersected = UnionToIntersection<
        { format320p: string } | 
        { format480p: string } |
        { format720p: string } |
        { format1080p: string } 
    >
    
    // T is { format320p: string }.... and T is a naked type
    // this means we can do a union of conditionals
    
    type Intersected = 
      UnionToIntersection<{ format320p: string }> |
      UnionToIntersection<{ format480p: string }> |
      UnionToIntersection<{ format720p: string }> |
      UnionToIntersection<{ format1080p: string }> 
    
    // expand it...
    
    type Intersected = 
      ({ format320p: string } extends any ? 
        (x: { format320p: string }) => any : never) extends 
        (x: infer R) => any ? R : never | 
      ({ format480p: string } extends any ? 
        (x: { format480p: string }) => any : never) extends 
        (x: infer R) => any ? R : never | 
      ({ format720p: string } extends any ? 
        (x: { format720p: string }) => any : never) extends 
        (x: infer R) => any ? R : never | 
      ({ format1080p: string } extends any ? 
        (x: { format1080p: string }) => any : never) extends 
        (x: infer R) => any ? R : never
    
    // unwrap first conditional: (T extends any ? (x: T) => any : never) 
    
    type Intersected = 
      (x: { format320p: string }) => any extends 
        (x: infer R) => any ? R : never | 
      (x: { format480p: string }) => any extends 
        (x: infer R) => any ? R : never | 
      (x: { format720p: string }) => any extends 
        (x: infer R) => any ? R : never | 
      (x: { format1080p: string }) => any extends 
        (x: infer R) => any ? R : never
    
    // conditional two!, inferring R!
    type Intersected = 
      { format320p: string } | 
      { format480p: string } | 
      { format720p: string } | 
      { format1080p: string }
    
    // But wait! `R` is inferred from a contra-variant position
    // I have to make an intersection, otherwise I lose type compatibility
    
    type Intersected = 
      { format320p: string } & 
      { format480p: string } & 
      { format720p: string } & 
      { format1080p: string }

    And that’s what we have been looking for! So applied to our original example:

    FormatKeys is now "format320p" | "format480p" | "format720p" | "format1080p". Whenever we add another format to the original union, the FormatKeys type gets updated automatically. Maintain once, use everywhere.

    Blog: https://fettblog.eu/typescript-union-to-intersection/

    https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance

  • 相关阅读:
    [Effective C++ 001]视C++为一个语言联邦
    DataGrid使用心得
    C#连接数据库(Oracle)
    一个编程菜逼当上.net程序员的故事
    ASP.NET 和 WinForm 弹出另存为对话框
    重新认识Attributes.add
    认识委托和事件
    自己写好记的Oracle的 Group By 、 Group By Rollup和Group By Cube基础
    卑微的人依然可以有美丽的梦想——一段让无数人感动的视频
    Ajax简介
  • 原文地址:https://www.cnblogs.com/Answer1215/p/16874524.html
Copyright © 2020-2023  润新知