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

 

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

 

参考资料:

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

posted @ 2017-05-04 21:11  老板丶鱼丸粗面  阅读(6702)  评论(0编辑  收藏  举报