11.TypeScript中的联合类型

联合类型(Union Types)

联合类型与交叉类型很有关联,但是使用上却完全不同。 偶尔你会遇到这种情况,一个代码库希望传入 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); // returns "    Hello world"

padLeft存在一个问题, padding参数的类型指定成了 any。 这就是说我们可以传入一个既不是 number也不是 string类型的参数,但是TypeScript却不报错。

let indentedString = padLeft("Hello world", true); // 编译阶段通过,运行时报错

在传统的面向对象语言里,我们可能会将这两种类型抽象成有层级的类型。 这么做显然是非常清晰的,但同时也存在了过度设计。 padLeft原始版本的好处之一是允许我们传入原始类型。 这样做的话使用起来既简单又方便。 如果我们就是想使用已经存在的函数的话,这种新的方式就不适用了。

代替 any, 我们可以使用 联合类型做为 padding的参数:

function padLeft(value: string, padding: string | number) {
    
    
    // ...
}

let indentedString = padLeft("Hello world", true); // errors 类型“boolean”的参数不能赋给类型“string | number”的参数

联合类型表示一个值可以是几种类型之一。 我们用竖线( |)分隔每个类型,所以 number | string | boolean表示一个值可以是 number, string,或 boolean。

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

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

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

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

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

这里的联合类型可能有点复杂,但是你很容易就习惯了。 如果一个值的类型是 A | B,我们能够 确定的是它包含了 A 和 B中共有的成员。 这个例子里, Bird具有一个 fly成员。 我们不能确定一个 Bird | Fish类型的变量是否有 fly方法。 如果变量在运行时是 Fish类型,那么调用 pet.fly()就出错了。

类型保护与区分类型(Type Guards and Differentiating Types)

联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为 Fish时怎么办? JavaScript里常用来区分2个可能值的方法是检查成员是否存在。 如之前提及的,我们只能访问联合类型中共同拥有的成员。

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

为了让这段代码工作,我们要使用类型断言:

let pet = getSmallPet();

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

用户自定义的类型保护

这里可以注意到我们不得不多次使用类型断言。 假若我们一旦检查过类型,就能在之后的每个分支里清楚地知道 pet的类型的话就好了。

TypeScript里的 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词:

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

在这个例子里, pet is Fish就是类型谓词。 谓词为 parameterName is Type这种形式, parameterName必须是来自于当前函数签名里的一个参数名。

每当使用一些变量调用 isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

// 'swim' 和 'fly' 调用都没有问题了

if (isFish(pet)) {
    
    
    pet.swim();
}
else {
    
    
    pet.fly();
}

注意TypeScript不仅知道在 if分支里 pet是 Fish类型; 它还清楚在 else分支里,一定 不是 Fish类型,一定是 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}'.`);
}

然而,必须要定义一个函数来判断类型是否是原始类型,这太痛苦了。 幸运的是,现在我们不必将 typeof x === "number"抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。

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}'.`);
}

这些* typeof类型保护*只有两种形式能被识别: typeof v === "typename"和 typeof v !== “typename”, "typename"必须是 “number”, “string”, "boolean"或 “symbol”。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

instanceof类型保护

如果你已经阅读了 typeof类型保护并且对JavaScript里的 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'
}

instanceof的右侧要求是一个构造函数,TypeScript将细化为:

  1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
  2. 构造签名所返回的类型的联合

以此顺序。

联合类型可以理解为,多个类型组成一个新的类型,它们之间没有交叉(有的话,就是交叉类型了)。谁的类型满足就谁上(前提是做好了类型保护)。

おすすめ

転載: blog.csdn.net/sinat_41212418/article/details/120836221