• TypeScript入门-高级类型


    高级类型

    交叉类型

    交叉类型,就是将多个类型合并为一个新的类型,这个新的类型具有这多个类型的成员,含有这几个类型的所有特性,是他们的综合体,像是集合的并集

    例子:

    function extend<T,U>(first: T, second: U): T&U {
        let result = <T & U>{};
        for (let id in first) {
            (<any>result)[id] = first[id];
        }
        for (let id in second) {
            if (!result.hasOwnProperty(id)) {
                (<any>result)[id] = second[id];
            }
        }
        return result;
    }
    
    class Person {
        constructor(public name: string) {
        }
    }
    
    interface Loggable {
        log(): void;
    }
    
    class myLoggable implements Loggable {
        log() {
            console.log('qwe');
        }
    }
    
    let jim = extend(new Person('qq'), new myLoggable());
    console.log(jim.name);
    jim.log();

    例子中jim有Person中的name属性也有myLoggable中的log()方法

    联合类型

    联合类型,不像是交叉类型是多个类型的合集,表示是这多个类型中的一种类型,像是集合中的交集,只有多个类型中共有的特性才可以被调用

    例如要向一个函数传递参数,这个参数可能是number也可能是string

    function padLeft(value: string, padding: any) {
        if (typeof padding === "number") {
            return Array(padding + 1).join(" ") + value;
        }
        if (typeof padding === "string") {
            return padding + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }
    
    padLeft("Hello world", 4); // "    Hello world"

    这里存在一个问题,将padding定义为any,表示我们可以传递任何值给padding,这个错误TypeScript是不会报错的,只有在编译时才会报错

    解决这个问题,可以采用联合类型,用竖线分割每个类型,表示是这几个类型中的一种

    function padLeft(value: string, padding: string | number) {
        ........
    }
    let f = padLeft("Hello world", true); // error

    如果一个值是联合类型,就只能访问这多个类型所共有的成员

    interface Bird {
        fly();
        layEggs();
    }
    
    interface Fish {
        swim();
        layEggs();
    }
    
    function getSmallPet(): Fish | Bird {
        ...
    }
    
    let pet = getSmallPet();
    pet.layEggs(); // okay
    pet.swim();    // errors

    getSmallPet的返回类型就是一个联合类型,所以pet就只能访问Bird和Fish的共有成员layEggs()

    在上面的例子中,我们不知道pet到底是那种类型,所以就不可能去访问哪些不是公共的成员,如果我们知道了pet的类型,就可以访问该类型的所有成员了

    类型保护和区分类型

    为了解决上面提到的具体类型的确定问题,需要引入类型断言(类型转换)

    let pet = getSmallPet();
    
    if ((<Fish>pet).swim) {
        (<Fish>pet).swim();
    } else {
        (<Bird>pet).fly();
    }

    问题显而易见,每次都需要对pet进行类型转换,麻烦

    用户自定义的类型保护

    类型保护就可以解决上述每次都要进行类型断言的缺点,类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型断言

    function isFish(pet: Fish | Bird): pet is Fish {
        return (<Fish>pet).swim !== undefined;
    }

    pet is Fish就是类型断言。 一个断言是 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。

    // 'swim' 和 'fly' 调用都没有问题了
    
    if (isFish(pet)) {
        pet.swim();
    }
    else {
        pet.fly();
    }

    TypeScript不仅知道在if里是Fish,而且还知道在else里是Bird类型

     

    typeof类型保护

    我们将之前的padLeft代码改用类型断言来实现

    function isNumber(x: any): x is number {
        return typeof x === "number";
    }
    
    function isString(x: any): x is string {
        return typeof x === "string";
    }
    
    function padLeft(value: string, padding: string | number) {
        if (isNumber(padding)) {
            return Array(padding + 1).join(" ") + value;
        }
        if (isString(padding)) {
            return padding + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }

    如果要按这样写就要为每个原始类型写一个函数,麻烦。TypeScript会把"typeof v === typeofname"和"typeof v !== typeofname"看做是类型保护,所以就不必为一个原始类型写一个函数,直接用typeof就可以了

    function padLeft(value: string, padding: string | number) {
        if (typeof padding === "number") {
            return Array(padding + 1).join(" ") + value;
        }
        if (typeof padding === "string") {
            return padding + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }

    instanceof类型保护

    instanceof类型保护是通过构造函数来细化类型的一种方式

    interface Padder {
        getPaddingString(): string
    }
    
    class SpaceRepeatingPadder implements Padder {
        constructor(private numSpaces: number) { }
        getPaddingString() {
            return Array(this.numSpaces + 1).join(" ");
        }
    }
    
    class StringPadder implements Padder {
        constructor(private value: string) { }
        getPaddingString() {
            return this.value;
        }
    }
    
    function getRandomPadder() {
        return Math.random() < 0.5 ?
            new SpaceRepeatingPadder(4) :
            new StringPadder("  ");
    }
    
    // 类型为SpaceRepeatingPadder | StringPadder
    let padder: Padder = getRandomPadder();
    
    if (padder instanceof SpaceRepeatingPadder) {
        padder; // 类型细化为'SpaceRepeatingPadder'
    }
    if (padder instanceof StringPadder) {
        padder; // 类型细化为'StringPadder'
    }

    类型别名

    类型别名就是给类型起一个别名,而且可以用于基础的数据类型

    type Name = string;
    type NameResolver = () => string;
    type NameOrResolver = Name | NameResolver;
    function getName(n: NameOrResolver): Name {
        if (typeof n === 'string') {
            return n;
        }
        else {
            return n();
        }
    }

    与接口不同,类型别名不会新建一个类型,只是类型的名字变了而已

    type Alias = { num: number }
    interface Interface {
        num: number;
    }
    declare function aliased(arg: Alias): Alias;
    declare function interfaced(arg: Interface): Interface;

    在上面的代码中interfaced返回值的类型是Interface,而aliased返回值的类型是对象字面量

    类型别名与接口还有一个地方不同的是类型别名不可以被extends和implements

    上面说了类型别名与接口的两个不同点,当然也有相同点,即都可以使用泛型

    type Tree<T> = {
        value: T;
        left: Tree<T>;
        right: Tree<T>;
    }

    字符串字面量类型

    字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合

    type Easing = "ease-in" | "ease-out" | "ease-in-out";
    class UIElement {
        animate(dx: number, dy: number, easing: Easing) {
            if (easing === "ease-in") {
                // ...
            }
            else if (easing === "ease-out") {
            }
            else if (easing === "ease-in-out") {
            }
            else {
                // error! should not pass null or undefined.
            }
        }
    }
    
    let button = new UIElement();
    button.animate(0, 0, "ease-in");
    button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

    只能从规定的三种类型中选择一种类型进行传递,其他的类型会报错

    可辨识联合

    可以合并字符串字面量类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式

    先定义三个将要联合的接口,每个接口都有kind属性,但是值不同,king属性可以作为可辨识的特征和标识

    interface Square {
        kind: "square";
        size: number;
    }
    interface Rectangle {
        kind: "rectangle";
         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;
        }
    }

    注意:如果在Shape中添加新的类型,那么在switch下也要添加相应的判断

    参考资料:

    TypeScript中文网 · TypeScript——JavaScript的超集

  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    你会卖掉自己的网上信息吗?大数据可能根本不属于你
    机器学习——TensorFLow实战房价预测
    数据库运作实践三三之歌(秘制口诀)
    1000行MySQL学习笔记,收藏版!
    吐血整理深度学习入门路线及导航【教学视频+大神博客+书籍整理】+【资源页】(2019年已经最后一个月了,你还不学深度学习吗???)
    Ubuntu Snap 简述
    参数传递
  • 原文地址:https://www.cnblogs.com/qqandfqr/p/6804214.html
Copyright © 2020-2023  润新知