TypeScript学习摘要(八)——高级类型

交叉类型(Intersection Types)

  • 某个类型的对象同时拥有多种类型的成员,交叉类型使用&创建

    function extend<T, U>(first: T, second: U): T & U {
        let result = <T & U>{};
        for (let id in first) {
            (<any>result)[id] = (<any>first)[id];
        }
        for (let id in second) {
            if (!result.hasOwnProperty(id)) {
                (<any>result)[id] = (<any>second)[id];
            }
        }
        return result;
    }
    class Person {
        constructor(public name: string) { }
    }
    interface Loggable {
        log(): void;
    }
    class ConsoleLogger implements Loggable {
        log() {
            // ...
        }
    }
    var jim = extend(new Person("Jim"), new ConsoleLogger());
    var n = jim.name;
    jim.log();
    

联合类型(Union Types)

  • 与交叉类型对应,联合类型表示一个值可以是几种类型之一,用|表示

  • 如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员

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

类型保护

用户自定义类型保护

  • 由于联合类型中,我们只能访问所有类型共有的成员,想要确切的判断是否为某个类型只能反复使用断言

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

    可以通过定义类型保护避免反复断言

    function isFish(pet: Fish | Bird): pet is Fish {
        return (<Fish>pet).swim !== undefined;
    }
    // 返回值pet is Fish 是一个类型谓词
    // pet为函数参数,Fish是联合类型的成员之一
    // 调用一次类型保护后,TypeScript会将变量缩减为那个具体的类型
    if (isFish(pet)) {
        pet.swim();
    }
    else {
        pet.fly();
    }
    

typeof 类型保护(基础类型保护)

  • 只有两种形式typeof v === "typename"typeof v !== "typename"
  • "typename"必须是 "number""string""boolean""symbol",与其它字符串比较不会被识别为类型保护

instanceof类型保护

  • 通过构造函数来细化类型
  • instanceof的右侧要求是一个构造函数,TypeScript将细化为:
    1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
    2. 构造签名所返回的类型的联合

可以为null的类型

  • 默认情况下,类型检查器认为 nullundefined可以赋值给任何类型。 nullundefined是所有其它类型的一个有效值;--strictNullChecks标记可以解决此错误

  • 当使用了--strictNullChecks,可选参数和属性会被自动地加上 | undefined

  • 可以通过js的写法使用类型保护去除null

    function f(sn: string | null): string {
        if (sn == null) {
            return "default";
        }
        else {
            return sn;
        }
    }
    function f(sn: string | null): string {
        return sn || "default";
    }
    

    或者通过断言的形式,写法是添加!后缀

    function fixed(name: string | null): string {
      function postfix(epithet: string) {
        return name!.charAt(0) + '.  the ' + epithet; // ok
      }
      name = name || "Bob";
      return postfix("great");
    }
    

类型别名(type)

  • 通过type 关键字取别名

  • 别名不会新建一个类型,只是已有类型的引用

    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 Tree<T> = {
        value: T;
        left: Tree<T>;
        right: Tree<T>;
    }
    
  • 类型别名和接口的区别

    • 接口可以在任何地方使用,而类型别名不能出现在声明右侧的任何地方

      type Yikes = Array<Yikes>; // error
      
    • 接口创建了一个新名字,而类型别名不会

    • 类型别名不能被 extendsimplements(自己也不能 extendsimplements其它类型)

    • 当无法通过接口来描述一个类型,并且需要用到联合类型或元组类型时会使用别名

字符串字面量类型

  • 字符串字面量可以实现类似枚举的效果

    // 联合类型    // 字符串字面量类型
    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.
            }
        }
    }
    
  • 字符串字面量可用于区分函数重载

    function createElement(tagName: "img"): HTMLImageElement;
    function createElement(tagName: "input"): HTMLInputElement;
    // ... more overloads ...
    function createElement(tagName: string): Element {
        // ... code goes here ...
    }
    

数字字面量类型

  • 感觉没啥特别用处

    function rollDie(): 1 | 2 | 3 | 4 | 5 | 6 {
        // ...
    }
    

枚举成员类型

  • 当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的,即枚举成员类型

    enum ShapeKind {
        Circle,
        Square,
    }
    

可辨识联合(Discriminated Unions)

  • 可辨识联合的三个要素

    1. 具有普通的单例类型属性— 可辨识的特征(即都有一个同样的属性用于分辨联合的成员)
    2. 一个类型别名包含了那些类型的联合— 联合(即用类型别名来组织联合)
    3. 此属性上的类型保护
    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;
        }
        // ... other operations go here ...
    }
    
    class ScientificCalculator extends BasicCalculator {
        public constructor(value = 0) {
            super(value);
        }
        public sin() {
            this.value = Math.sin(this.value);
            return this;
        }
        // ... other operations go here ...
    }
    
    let v = new ScientificCalculator(2)
            .multiply(5)
            .sin()
            .add(1)
            .currentValue();
    

索引类型(Index types)

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n]);
}

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: 'Jarid',
    age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, string[]

索引类型查询操作符

  • keyof T
  • keyof T等同于 T上已知的公共属性名的联合,如 keyof Person完全可以与 'name' | 'age' 替换

索引访问操作符

  • T[K] 类似于对象索引

映射类型

  • 通过转化的方式从旧类型中创建新类型,如

    interface PersonPartial {
        name: string;
        age: number;
    }
    // 统一转化为只读
    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    }
    // 统一转化为可选
    type Partial<T> = {
        [P in keyof T]?: T[P];
    }
    
  • typescript还内置了一些映射类型,可以直接使用,如

    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    }
    type Record<K extends string, T> = {
        [P in K]: T;
    }
    // 预定义的有条件类型
    Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
    Extract<T, U> -- 提取T中可以赋值给U的类型。
    NonNullable<T> -- 从T中剔除null和undefined。
    ReturnType<T> -- 获取函数返回值类型。
    InstanceType<T> -- 获取构造函数类型的实例类型
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容