联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为Fish
时怎么办? JavaScript里常用来区分2个可能值的方法是检查成员是否存在。 如之前提及的,我们只能访问联合类型中共同拥有的成员。
let pet = getSmallPet();
// 每一个成员访问都会报错
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
为了让这段代码工作,我们要使用类型断言:
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将细化为:
- 此构造函数的
prototype
属性的类型,如果它的类型不为any
的话 - 构造签名所返回的类型的联合
以此顺序。