Intersection Types
它是把多个类型组合成一个类型,它使得你使用这个类型的时候享有定义的所有类型的特征。
例如:
Person & Serializable & Loggable
即你定义的具有这个类型的对象拥有了这三种类型包括的所有成员。
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;
jim.log();
Union Types
与Intersection Types
类似于集合运算里面的并和差的关系。
Union Types
代表多选一。使用(|)
,来分隔类型。number | string | boolean
function padLeft(value: string, padding: string | number) {
// ...
}
let indentedString = padLeft("Hello world", true); // errors during compilation
当一个值是Union Types
,我们只能使用所有类型里面公有的属性,因为其他的属性不确实是否有,所以使用,TypeScript会报错。
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
Type Guards and Differentiating Types
Union types
对于建模情形是非常有用的,这种情形下,他们所带的类型的中有重叠的值。
例如:我们想知道在什么情况下是某一特定类型,这样我们就能有条件的使用它。
在一般的JavaScript中,我们判断是否存在特定的成员来判断。
let pet = getSmallPet();
// Each of these property accesses will cause an error
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
然而在TypeScript
中只允许访问类型共有的值。
所以我们可以使用type assertion
let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
User-Defined Type Guards
因为上面我们用了多次type assertion
,所以我们需要改进。只做一次判断,就知道接下来的分支里面是什么类型。
所以TypeScript
引入了 type guard
在一个函数的返回类型加上parameterName is Type
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
如果变量的类型和源类型兼容,也就是确实是 Fish | Bird
,那么TypeScript会把变量的类型缩小到特定的类型,也就是我们的断言类型。
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
这样TypeScript既知道if
分支里面是Fish
,也知道else分支里面是Bird
typeof type guards
当我们需要做分支判断的时候,引入上面的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 guard type
,来帮助减少工作。
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 guard type
是如下两种格式:
typeof x === "number"
typeof x !== "number"
其中属于typeof guard type
原始类型只包括:number, string, boolean, symbol
,你也可以自己定义比较其他的类型,但是不会被认为是typeof guard type
instanceof type guards
格式如下:
instance instanceof constructor
当使用这个保卫类型的时候,TypeScript会将变量的类型按顺序缩小到一下两个范围:
- 如果这个构造函数的原型的类型不是
any
,就会缩小到这个原型类型 - 否则返回这个构造函数签名的
union types
类型
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(" ");
}
// Type is 'SpaceRepeatingPadder | StringPadder'
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // type narrowed to 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // type narrowed to 'StringPadder'
}
Nullable types
TypeScript有两个特殊类型。null
和 undefined
,他们在没有限制的条件下能够赋值给任何类型。
如果加上--strictNullChecks
选项,只能赋值给各自的类型和void
Type guards and type assertions
因为可以为空的类型(nullable
)是union type
,所以你需要排除null
,方法和JavaScript中一样。
- 方法一
function f(sn: string | null): string {
if (sn == null) {
return "default";
}
else {
return sn;
}
}
- 方法二
function f(sn: string | null): string {
return sn || "default";
}
- 方法三,如果编译器不能排除
null
,你就需要用type assertions
来手动排除。
例如在嵌套的函数中,编译器不能追踪到所有调用(除非这个嵌套函数是立即执行的(IIFE)),尤其是这个嵌套函数作为返回的结果返回给外面的函数时。
不知道在哪里会调用这个嵌套函数,所以在函数体执行时,也就不不知道参数的类型,所以编译器无法排除null
。
手动排除方法:通过identifier!
排除null
function broken(name: string | null): string {
function postfix(epithet: string) {
return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null
}
name = name || "Bob";
return postfix("great");
}
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob";
return postfix("great");
}
其实综上,就是非立即执行的嵌套函数在调用时,由于它存在的那个函数里已经执行完毕,所以它引用的那个函数中的变量类型以及不可知了。
Type Aliases
它能给类型创建一个名字。Aliases
不会创建新类型,只会创建一个新名字来引用这个类型。
给原始类型创建别名不是很有用,尽管它能用于文档形式。
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 aliases
也可以是一个泛型。
type Container<T> = { value: T };
我们能在type alias
的属性中引用自己。构建树形类型。
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
与intersection types
组合出一些非常高级的类型。
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
但是,我们不能在别名申明语句等号右边使用这个别名。
type Yikes = Array<Yikes>; // error
Interfaces vs Type Aliases
- 接口创建了一个新名字,而类型别名只是返回一个对象字面量
- 接口可继承扩展,或被实现。类型别名不能。
因为一个理想的软件是可扩展的。所以我们尽可能使用Interface
,如果在遇到Interface表达不了的shape
时,或者需要使用union types
和tuple type
的时候,才需要使用类型别名。
String literal type
- 与
union
,guard type
,alias
一起使用来获得一个类枚举的string
类型。
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
- 也用来区分真正函数和重载函数
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
Discriminated Unions
与union
,guard type
,alias
一起使用来构建一个高级的模式:discriminated unions
,也被成为agged unions
,或algebraic data types
。
discriminated unions
在函数式编程中很有用。有些编程语言自动区分unions
,而TypeScript是构建在现代JavaScript上的,所以不自动区分。
discriminated unions
包括以下三个要素。
- 有着相同的,字符串字面量的属性的类型集 -
the discriminant.
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
- 用一个类型别名把这些类型
union
, -the union.
type Shape = Square | Rectangle | Circle;
- 通过
type guards
来对这个相同属性名下的不通知作判断。
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;
}
}