交叉类型
包含所有类型的特性。比如 Person & Serializable & Loggable
交叉类型同时拥有了 Person
、 Serializable
和 Loggable
这三种类型的成员。
联合类型
联合类型表示一个值可以是几种类型之一。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
自定义类型保护
类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型谓词:
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
在这个例子里, pet is Fish
就是类型谓词。 谓词为 parameterName is Type
这种形式, parameterName
必须是来自于当前函数签名里的一个参数名。
别名
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
};
type Test = (name: Test) => void;
// fail:
// Type alias 'ArrayConfig' circularly references itself.
type ArrayConfig = Array<number | ArrayConfig>;
类型别名不能被 extends
和 implements
。
可辨识联合( Discriminated Unions )
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
首先我们声明了将要联合的接口。每个接口都有 kind 属性但有不同的字符串字面量类型。 kind 属性称做可辨识的特征或标签,其它的属性则特定于各个接口。注意,目前各个接口间是没有联系的。下面我们把它们联合到一起:
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;
}
}
完整性检查
当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。比如,如果我们添加了 Triangle
到 Shape
,我们同时还需要更新 area
:
type Shape = Square | Rectangle | Circle | Triangle;
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;
}
// should error here - we didn't handle case "triangle"
}
有两种方式可以实现。 首先是启用 --strictNullChecks
并且指定一个返回值类型:
function area(s: Shape): number { // error: returns number | undefined
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;
}
}
因为 switch
没有包涵所有情况,所以 TypeScript
认为这个函数有时候会返回 undefined
。如果你明确地指定了返回值类型为 number
,那么你会看到一个错误,因为实际上返回值的类型为 number | undefined
。
第二种方法使用 never
类型,编译器用它来进行完整性检查:
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
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;
default: return assertNever(s); // error here if there are missing cases
}
}
这里, assertNever
检查 s
是否为 never
类型 — 即为除去所有可能情况后剩下的类型。如果你忘记了某个 case ,那么 s 将具有一个真实的类型并且你会得到一个错误。这种方式需要你定义一个额外的函数,但是在你忘记某个 case 的时候也更加明显。
网友评论