美文网首页
TypeScript 高级类型

TypeScript 高级类型

作者: kuxingseng686 | 来源:发表于2020-09-08 13:56 被阅读0次

    一、交叉类型

    交叉类型将多个类型合并为一个新的类型,新的具有所有参与合并的类型的特性,本质上是一种并的操作。形式如下:

    T & U
    //function
    function extend<T , U>(first: T, second: U) : T & U {
        let result: <T & U> = {}
        for (let key in first) {
            result[key] = first[key]
        }
        for (let key in second) {
            if(!result.hasOwnProperty(key)) {
                result[key] = second[key]
            }
        }
        return result
    }
    
    //class 
    class BaseActorCore<T extends BaseActorView, D extends BaseActorModel>
    

    二、联合类型

    联合类型用 | 分割每一个类型,表示只能取其中的一种类型,比如: number | string | boolean 的类型只能是这三个的一种,不能共存。
    如果一个值的类型是联合类型,那么我们只能访问它们中共有的属性或者方法,本质上是一种交的关系,比如: [color=#ff0000]aUHI[/color]

    interface Cat {
        eat(food) : void
        miao() : string
    }
    interface Dog {
        eat(food) : void
        wang() : string
    }
    
    function getPet() : Cat | Dog {
        ...
    }
    let pet = getPet()
    pet.eat() //正确
    pet.miao() //报错
    

    三、类型保护与类型区分

    联合类型让一个值可以为不同的类型,但随之带来的问题就是访问非共同方法时会报错。那么该如何区分值的具体类型,以及如何访问共有成员?

    1.使用类型断言

    let pet = getPet()
    if((<Cat>pet).miao) {
        (<Cat>pet).miao()
    } else {
        (<Dog>pet).wang()
    }
    

    可见使用类型断言就不得不写一堆蹩脚的尖括号,那么还没有更简洁明了办法来判断类型呢?

    2.使用类型保护

    function isCat(pet: Cat | Dog) : pet is Cat {
        return (<Cat>pet).miao !== undefined
    }
    

    这种param is SomeType 的形式就是类型保护,我们可以用它来明确一个联合类型变量的具体形式,在调用时ts就会将变量缩减为该具体类型,调用就是合法的了

    if (isCat(pet)) {
        pet.miao() //正确
    } else {
        pet.wang()
    }
    

    允许这么做是因为:typescript能够通过类型保护知道if语句里的pet类型一定是Fish类型,而且else语句里的pet类型一定不是Fish类型,那么就是Bird类型了

    3.type 和instanceof

    当我们使用了typeof和instanceof后,typescript就会自动限制类型为某一具体类型,从而我们可以安全地在语句体内使用具体类型的方法和属性,如:

    function show(param: number | string) {
        if (typeof param === 'number') {
            console.log(`${param} is number`)
        } else {
            console.log(`${param} is string`)
        }
    }
    

    但是typeof只支持number、string、boolean或者symbol(只有这些情况下可以被认为是类型保护)
    如果是类,我们可以用instanceof

    let an = getSomeKindType()
    if (an instanceof Animal) {
        an // 此时aa会细化为Animal类型
    }
    if (an instanceof People) {
        an //此时aa会细化为People类型
    }
    

    ts要求instanceof右侧是一个构造函数,而ts会将其细化为:

    • 此构造函数的prototype属性的类型
    • 构造函数所返回的类型的联合

    4. 可为null的类型

    null和undefined可以赋给任何的类型,因为它们是所有其他类型的一个有效值,如:

    // 以下语句均合法
    let x1: number = null
    let x2: string = null
    let x3: boolean = null
    let x4: undefined = null
    let y1: number = undefined
    let y2: string = undefined
    let y3: boolean = undefined
    let y4: null = undefined
    

    在typescript里,我们可以使用–strictNullChecks标记,开启这个标记后,当我们声明一个变量时,就不会自动包含null或undefined,但是我们可以手动使用联合类型来明确包含,如:

    let x = 123
    x = null // 报错
    let y: number | null = 123
    y = null // 允许
    y = undefined // 报错,`undefined`不能赋值给`number | null`
    

    当开启了–strictNullChecks,可选参数/属性就会被自动地加上| undefined,如:

    function foo(x: number, y?: number) {
        return x + (y || 0)
    }
    foo(1, 2) // 允许
    foo(1) // 允许
    foo(1, undefined) // 允许
    foo(1, null) // 报错,不允许将null赋值给`number | undefined`类型
    

    四、类型别名

    类型别名可以给现有的类型起个新名字,它和接口很像但又不一样,因为类型别名可以作用于原始值、联合类型、元组及其他任何需要手写的类型,语法如:

    type Name = string
    

    别名不会新建一个类型,它只会创建一个新的名字来引用现有类型。所以在VSCode里将鼠标放在别名上时,显示的是所引用的那个类型

    1.范性别名

    type Container<T> = {
        value: T
    }
    
    let name: Container<string> = {
        value: 'hello'
    }
    

    2. 和接口的区别

    • 别名不能被extends和implements
    • 错误信息、鼠标悬停时,不会使用别名,而是直接显示为所引用的类型

    3. 字符串字面量类型

    字符串字面量类型允许我们定义一个别名,类型为别名的变量只能取固定的几个值,如:

    type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'
    let x1: Easing = 'uneasy' // 报错: Type '"uneasy"' is not assignable to type 'Easing'
    let x2: Easing = 'ease-in' // 允许
    

    4. 可辨识联合

    可以合并字符串字面量类型、联合类型、类型保护和类型别名来创建可辨识联合的高级模式(也称为标签联合或者代数数据类型),具有3个要素:

    具有普通的字符串字面量属性——可辨识的特征
    一个类型别名,用来包含了那些类型的联合——联合
    此属性上的类型保护
    创建一个可辨识联合类型,首先需要声明将要联合的接口,每个接口都要有一个可辨识的特征,如(kind属性):

    interface Square {
        kind: 'square'
        size: number
    }
    
    interface Rectangle {
        kind: 'rectangle'
        width: number
        height: number
    }
    
    interface Circle {
        kind: 'circle'
        radius: number
    }
    

    现在,各个接口之间还是没有关联的,所以我们需要使用类型别名来联合这几个接口,如:

    type Shape = Square | Rectangle | Circle
    

    现在,使用可辨识联合,如:

    function area(s: Shape) {
        switch (s.kind) {
            case 'square':
                return s.size * s.size
            case 'rectangle':
                return s.height * s.width
            case 'circle':
                return Math.PI * s.radius ** 2
        }
    }
    

    五、多态的this类型

    多态的this类型表示的是某个包含类或接口的子类型,例子如:

    class BasicCalculator {
        public constructor(protected value: number = 0) {
        }
        public currentValue(): number {
            return this.value
        }
        public add(operand: number): this {
            this.value += operand
            return this
        }
        public multiply(operand: number): this {
            this.value *= operand
            return this
        }
    }
    
    let v = new BasicCalculator(2).multiply(5).add(1).currentValue() // 11
    

    由于使用了this类型,当子类继承父类的时候,新的类就可以直接使用之前的方法,而不需要做任何的改变,如:

    class ScientificCalculator extends BasicCalculator {
        public cconstructor(value = 0) {
            super(value)
        }
        public sin() {
            this.value = Math.sin(this.value)
            return this
        }
    }
    let v = new BasicCalculator(2).multiply(5).sin().add(1).currentValue()
    

    如果没有this类型,那么ScientificCalculator就不能够在继承BasicCalculator的同时还保持接口的连贯性。因为multiply方法会返回BasicCalculator类型,而BasicCalculator没有sin方法。然而,使用this类型,multiply就会返回this,在这里就是ScientificCalculator

    六、索引类型

    索引类型能使编译器能够检查使用了动态属性名的代码,比如我们想要写一个函数,它可以选取对象中的部分元素的值,那么:

    function pluck<T, K entends keyof T>(o: T, names: K[]) : T[K][] {
        return names.map(n => o[n])
    }
    
    interface Person {
        name: string
        age: number
    }
    
    let p: Person = {
        name: 'LL',
        age: 18
    }
    let res = pluck(p, ['name']) //允许
    

    以上代码解释如下:
    1)首先,使用keyof关键字,它是索引类型查询操作符,它能够获得任何类型T上已知的公共属性名的联合。如例子中,keyof T相当于’name’ | ‘age’
    2)然后,K extends keyof T表明K的取值限制于’name’ | ‘age’
    3)而T[K]则代表对象里相应key的元素的类型,所以在例子中,p对象里的name属性,是string类型,所以此时T[K]相当于Person[name],即相当于类型string,所以返回的是string[],所以res的类型为string[]
    所以,根据以上例子,举一反三有:

    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
        return obj[key]
    }
    let obj = {
        name: 'LL',
        age: 18,
        male: true
    }
    let x1 = getProperty(obj, 'name') // 允许,x1的类型为string
    let x2 = getProperty(obj, 'age') // 允许,x2的类型为number
    let x3 = getProperty(obj, 'male') // 允许,x3的类型为boolean
    let x4 = getProperty(obj, 'hobby') // 报错:Argument of type '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.
    

    索引类型和字符串索引签名
    keyof和T[K]与字符串索引签名进行交互,如果有一个带有字符串索引签名的类型,那么keyof T为string,且T[string]为索引签名的类型,如:

    interface Demo<T> {
        [key: string]: T
    }
    let keys: keyof Demo<boolean> // keys的类型为string
    let value: Demo<number>['foo'] // value的类型为number
    

    七、映射类型
    我们可能会遇到这么一些需求:
    1)将一个现有类型的每个属性都变为可选的,如:

    interface Person {
        name: string
        age: number
    }
    

    可选版本为:

    interface PersonPartial {
        name?: string
        age?: number
    }
    

    2)或者将每个属性都变为只读的,如:

    interface PersonReadonly {
        readonly name: string
        readonly age: number
    }
    

    而现在typescript为我们提供了映射类型,能够使得这种转化更加方便,在映射类型里,新类型将以相同的形式去转换旧类型里每个属性,如以上例子可以改写为:

    type Readonly<T> = {
        readonly [P in keyof T]: T[P]
    }//ts 源码
    type Partial<T> = {
        [P in keyof T]?: T[P]
    }//ts源码
    type PersonReadonly = Readonly<Person>
    type PersonPartial = Partial<Person>
    

    另外ts还给我们提供了Required,Pick,Record,几种常用到的类型,下面是它们的源码:

    type Required<T> = {
        [P in keyof T]-?: T[P];
    }
    
    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    }
    
    type Record<K extends keyof any, T> = {
        [P in K]: T;
    }
    
    type personRequired = Required<Person>;
    // personRequired === {name: string; age: number}
    
    type personPick = Pick<Person, "name">;
    // person5 === {name: string}
    
    type personRecord = Record<'name' | 'age', string>
    // personRecord === {name: string; age: string}
    

    我们还可以写出更多的通用映射类型,如:

    // 可为空类型
    type Nullable<T> {
        [P in keyof T]: T[P] | null
    }
    
    // 包装一个类型的属性
    type Proxy<T> = {
        get(): T
        set(value: T): void
    }
    type Proxify<T> = {
        [P in keyof T]: Proxy<T[P]>
    }
    function proxify(o: T): Proxify<T> {
        // ...
    }
    let proxyProps = proxify(props)
    

    由映射类型进行推断(拆包)
    上面展示了如何包装一个类型,那么与之相反的就有拆包操作,示例如:

    function unproxify<T>(t: Proxify<T>): T {
        let result = <T>{}
        for (const k in t) {
            result[k] = t[k].get()
        } 
        return result
    }
    let originalProps = unproxify(proxyProps)
    

    相关文章

      网友评论

          本文标题:TypeScript 高级类型

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