The Night We Stood Lyn
  1. 1 The Night We Stood Lyn
  2. 2 Last Surprise Lyn
  3. 3 Libertus Chen-U
  4. 4 Quiet Storm Lyn
  5. 5 Warcry mpi
  6. 6 One Last You Jen Bird
  7. 7 Hypocrite Nush
  8. 8 かかってこいよ NakamuraEmi
  9. 9 Time Bomb Veela
  10. 10 Life Will Change Lyn
  11. 11 Flower Of Life 发热巫女
2018-02-07 21:32:35

TypeScript的高级类型

在TypeScript的基础类型之上还实现了一些较为复杂的些高级类型,平时开发中可能用的不多,但面对特定场景会有很好的效果,有必要一一了解。


交叉类型

交叉类型和&运算符的思想类似,该类型可以让我们把现有的多种类型叠加到一起成为一种类型,它包含所有类型的特性。通常在使用mixin的场合使用该类型。例子如下:

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; // ok
jim.log() // ok

联合类型

有&就有|,联合类型和|运算符的思想类似,表示该类型可以是联合类型成员中的任意一个。联合类型在未判断出真正类型之前只能调用这些类型共有的方法,除非利用类型推论判断。例子如下:

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // ok
pet.swim();  // error

类型保护

既然联合类型可以让一个值为不同的类型,那么访问非共同方法时就会报错,如何区分值的具体类型并访问不同的成员呢?首先可以使用类型断言,接上例如下:

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

可以看到类型断言在每次使用变量时都不得不写尖括号,非常麻烦。进一步的,可以使用类型保护来明确一个联合类型变量的具体类型,例子如下:

function isFish(pet: Bird | Fish): pet is Fish {
    return (<Fish>pet).swim !== undefined
}
if (isFish(pet)) {
    pet.swim() // 保护为Fish类型,不会报错
} else {
    pet.fly() // else语句中智能推断为Bird类型,不会报错
}

以上的类型保护使用了parameterName is Type的形式,可以称为类型谓词保护

除了谓词的类型保护外,typeof和instanceof也拓展了类型保护功能,当在TS中使用以上两者后,就会自动限制类型为某一具体类型。需要注意typeof只支持number,string,boolean,symbol的类型保护。例子如下:

function get(): number | string{
    return 'test';
}
let test = get();
if(typeof test === 'string'){
    console.info(test.length); // 这里由于typeof确定了test类型是string,可以直接使用非共有属性length
}

对于类的类型保护则使用instanceof,要求判断类型有构造函数,TS会将其保护为构造函数的原型对象的类型,如果原型对象的类型为any则保护为构造签名返回类型的联合。例子如下:

interface Padder {
    getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
    repeat: string = '张三丰';
    getPaddingString() {
        return 'space';
    }
}
class StringPadder implements Padder {
    age: number = 15,
    getPaddingString() {
        return 'string';
    }
}
function getRandomPadder(): Padder {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder() :
        new StringPadder();
}
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
    console.log(padder.repeat); // 类型保护为SpaceRepeatingPadder,不报错
}
if (padder instanceof StringPadder) {
    console.log(padder.age); // 类型保护为StringPadder, 不报错
}

类型别名

类型别名可以给类型起一个新名字也可以作用于其他任何需要手写的类型。别名不会新建一个类型,只会创建一个名字来引用被命名的类型。类型别名也支持泛型,但不能被extends和implements扩展。例子如下:

type Container<T> = {
    value: T
}

let name: Container<string> = {
    value: 'RuphiLau'
}

字符串,数字字面量类型

该类型允许指定为固定的字符串值或数字,通常用该类型限制参数的输入。例子如下:

function test(param1: 'test1' | 'test2' | 1){

}
test('test'); // error,参数只能是test1, test2或1

可辨识联合

这个高级类型用到了字符串字面量类型,联合类型,类型保护,类型别名来创建。必须具备以下3个要素:

  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
    }
}

let v = new BasicCalculator(2).multiply(5).add(1).currentValue()

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

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();

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


索引类型

使用索引类型编译器就能检查使用了动态属性名的代码,例如从一个对象中选取部分属性的值,例子如下:

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 p: Person = {
    name: 'RuphiLau',
    age: 21
}

let res = pluck(p, ['name'])  // ok 

首先使用了keyof关键字,能够获得任何类型T上已知公共属性名的联合,例子中即为'name' | 'age'。然后K extends keyof T表示K的类型继承自T。 T[K]则代表对象里相应属性的类型,此例中T[K]相当于Person[name]的类型,即为string,所以返回类型为string[]。


映射类型

有时候我们需要根据一个已知类型创建对应的新类型,例如将每个属性变为可选的,TS提供了映射类型来做这件事。例子如下:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}
type Partial<T> = {
    [P in keyof T]?: T[P];
}
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;

重点在于in关键字,其会遍历字符串字面量联合的Keys的并设置P为属性名。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「TypeScript」 标签。