美文网首页js css html
第十三节:TypeScript 条件类型

第十三节:TypeScript 条件类型

作者: 时光如剑 | 来源:发表于2022-04-16 07:54 被阅读0次

    条件类型

    1. 条件类型

    条件类型有助于描述输入和输出类型之间的关系

    1.1 条件类型语法

    条件类型就是根据一个条件表达式来进行类型检测, 类似于三目运算符

    // 语法
    T extends U ? X: Y
    

    TU 的子类型,则类型为 X,否则类型为 Y。若无法确定 T 是否为 U 的子类型,则类型为 X | Y

    例如:

    // 接口
    interface Person {
      name: string
      age: number
    }
    
    
    // 扩展接口
    interface Student extends Person{
      classId: string
    }
    
    // 根据Student是不是继承Person接口来判断返回string类型还是number类型 
    type Example = Student extends Person ? string : number
    // type Example = string
    
    type Example2 = RegExp extends Person ? string : number
    // type Example2 = number
    

    示例中,根据接口Student 是否继承Person接口,来决定类型别名Example 具体是string 类型还是number类型

    如不是判断类型的子类型,则返回联合类型

    例如:

    type Person<Type> = Type extends boolean ? string : number
    
    type StringOrNumber = Person<any>
    // type StringOrNumber = string | number
    

    代码解释: 泛型Person在使用时传入了any, any类型可是任何类型, 如果是boolean, 则返回string类型, 如果不是boolean,则返回number类型, 此时TypeScript就不好抉择了, 直接返回string | number的联合类型

    1.2 条件类型与泛型一起使用

    条件类型可能不会立即有用, 但是条件类型的强大之处在于他们和泛型一起使用

    例如,我们采用以下createLabel功能

    // 定义id接口
    interface IdLabel{
      id: number
    }
    
    // 定义name接口
    interface NameLabel {
      name: string
    }
    
    
    // 定义重载更具入参决定返回类型
    function createLabel(id:number):IdLabel;
    function createLabel(name:string):NameLabel;
    function createLabel(nameOrId:string | number):NameLabel | IdLabel;
    function createLabel(nameOrId:string | number):NameLabel | IdLabel{
      if(typeof nameOrId === 'string'){
        return {name:nameOrId}
      }else{
         return {id:nameOrId}
      }
    }
    

    createLabel的这些重载描述了一个JavaScript函数, 该函数根据其输入的类型进行选择

    请注意:示例中我们必须创建三个重载, 一个用于确定的每种情况(string 或者 number)类型, 一个用于最一般的情况(string | number)联合类型

    相反的,我们可以将该逻辑编写为条件类型

    type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel
    

    首先确定的泛型类型T限定必须继承number | string, 然后在根据传入的泛型更详细的是否extends继承number类型

    ,如果为true 则返回IdLabel接口, 否则返回NameLabel接口

    此时我们就可以使用该条件类型将重载简化为没有重载的单个函数

    例如:

    function createLabel<T extends string | number>(nameOrId:T):NameOrId<T>{
     throw "unimplemented";
    }
    
    const namelabel = createLabel('hello')
    // const namelabel: NameLabel
    
    const idlabel = createLabel(10)
    // const idlabel: IdLabel
    
    const nameidlabel = createLabel(Math.random() > 0.5 ? 'hello' : 42)
    // const nameidlabel: NameLabel | IdLabel
    
    

    2. 条件类型约束

    通常, 条件类型的检查会为我们提供一些新信息, 就像使用类型保护缩小类型范围, 可以给我们提供一个更具体的类型一样.

    条件类型的真正分支将会进一步限制我们检查类型的泛型

    2.1 泛型继承约束

    例如.让我们采取以下措施

    type MessageOf<T> = T['message']
    // 报错: message 无法用于索引类型T
    

    在例子中, TypeScript出错的原因在于T类型不知道有一个message的属性.

    因此我们可以通过extends约束以下T类型, 这样TypeScript就不会在抱怨了

    也就是说泛型的传入必须满足具有message属性

    例如:

    type MessageOf<T extends {message: unknown}> = T['message']
    
    interface Email {
      message: string
    }
    
    type EmailMessage = MessageOf<Email>
    // type EmailMessage = string
    

    但是,这样的约束会带来另外一个问题, 那就是MessageOf类型不能使用没有message属性的类型,例如

    interface Dog{
      name: string
    }
    
    type DogMessage = MessageOf<Dog>
    // 报错: 类型Dog 不满足约束 {message:unknown}
    // 类型Dog 缺少`message`属性, 但是类型{message:unknown} 需要该属性
    

    那么如果我们想MessageOf采用任何类型, 并且在message属性不可用时, 默认为never, 那该怎么办呢

    2.2 条件约束

    我们可以尝试移出约束,并使用条件类型来做到这一点

    type MessageOf<T> = T extends {message: unknown } ? T['message'] : never;
    
    interface Email {
      message: string
    }
    
    interface Dog{
      name: string
    }
    type EmailMessage = MessageOf<Email>
    // type EmailMessage = string
    
    type DogMessage = MessageOf<Dog>
    // type DogMessage = never
    

    在条件为true的分支中,TypeScript知道T类型会有一个message属性, 为false的分支中直接返回never类型

    作为另外一个示例, 我们还可以编写一个名为Flatten的类型别名, 用于将数组类型展平, 获取数组元素的类型,如果不是数组类型则不理, 传入什么类型返回什么类型

    type Flatten<T> = T extends any[] ? T[number] : T
    
    type Str = Flatten<string[]>
    // type Str = string
    
    type Num = Flatten<number>
    // type Num = number
    

    示例中, 当Flatten 给定一个数组类型是, 它会通过索引访问number来获取string[]的元素类型. 并返回

    如果给定的不是数组类型, 则返回给定的类型

    3. 在条件类型中推断

    条件类型为我们提供了一种方法就是使用infer关键字来推断我们在真实分支中类型,

    例如:我们可以推断数组元素的类型,而不是像之前一样使用索引访问类型, 并手动的获取 元素类型

    type Flatten<T> = T extends Array<infer Item> ? Item : T
    

    在这里,我们使用了infer关键字声明性地引入了一个新的泛型类型变量Item, 而不是在条件在条件为true的分支中通过索引访问数组元素的类型. 这使我们不必考虑如何挖掘和探索我们感兴趣的类型结构

    也可以使用infer关键字编写一些有用的辅助类型别名,

    例如,对于简单的情况,我们可以从函数类型中提取返回类型:

    // 获取函数返回类型
    type GetReturnType<T> = T extends (...arg:never[]) => infer Return ? Return : never
    
    
    type Num = GetReturnType<() => number>
    // type Num = number
    
    
    type Str = GetReturnType<(x:string) => string>
    // type Str = string
    
    
    type Bool = GetReturnType<(a:boolean, b:boolean) => boolean[]>
    // type Bool = boolean[]
    

    4. 分布式条件类型

    当条件类型作用于泛型时, 它们给定联合类型时变得可分配

    例如,采用如下措施

    type ToArray<Type> = Type extends any ? Type[] : never;
    

    如果我们将联合类型插入ToArray,那么条件类型将应用于该联合的每个成员。

    type ToArray<Type> = Type extends any ? Type[] : never;
     
    type StrArrOrNumArr = ToArray<string | number>;
    // type StrArrOrNumArr = string[] | number[]
    

    这里发生的情况是StrArrOrNumArr分布在:

      string | number;
    

    并将联合的每个成员类型映射到有效的内容:

      ToArray<string> | ToArray<number>;
    

    这给我们留下了:

    string[] | number[];
    

    通常,分配性是期望的行为。extends为避免这种行为,您可以用方括号将关键字的每一侧括起来。

    type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
     
    // 'StrArrOrNumArr' is no longer a union.
    type StrArrOrNumArr = ToArrayNonDist<string | number>;
               
    type StrArrOrNumArr = (string | number)[]
    

    相关文章

      网友评论

        本文标题:第十三节:TypeScript 条件类型

        本文链接:https://www.haomeiwen.com/subject/uavxertx.html