高级类型

1. 交叉类型

符号: &

如: Person & Serializable & Loggable 返回的类型,同时拥有了这三个类型的成员。

这通常出现在 extendmixins

interface A {
    a: string;
}

interface B {
    b: string;
}

let a = { a:'1' };
let b = {b:'1'};
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;
}

extend(a,b)

T & U 即有 a 属性,也有 b 属性。

2. 联合类型

符号: |

表示几种类型之一,如 number | string | boolean 表示一个值可以是 numberstring 或者 boolean类型

如果一个值的类型是联合类型,那么只能访问所有类型中共有的成员:

为了使以上代码不报错,我们可以使用类型断言:

如果我们多次用到 pet.a, 那么就要使用多次类型断言

因此引入了类型保护的概念

3. 类型保护

自定义类型保护

要定义一个类型保护,只要定义一个函数,返回值是一个类型谓词即可:

pet is A就是类型谓词,谓词为 parameterName is Type, parameterName 必须是来自当前函数签名里的参数名。

这样使用变量调用isFish时,TypeScript会将变量缩减为 is 之后的那个类型,只要该类型与变量的原始类型兼容。

typeof类型保护

再次回顾之前的 isFish 函数,也就是说通过调用该函数,那么就能确保某个作用于里的类型,如isFish为true,那么

这段代码里,a就是A类型。

那如果我们要判断某个值是不是原始类型,如 string, number 可以通过 typeof 来判断:

但是,每次原始类型都得写一个isXxx函数来判断,这太过麻烦。好在TypeScript 直接将 typeof x === 'string' 识别为类型保护:

typeof 这种类型保护只能这样使用, typeof v === typename 或者 typeof v !== typename(是指其他判断如 ['type1','type2'].includes(typeof v) 无效吧)。

此外,typename 只能是 number,string,boolean 或者 symbol

instanceof类型保护typeof 类似

4. 可以为 null 的类型

通常 null 和 undefined 可以赋值给任何其他的类型,即使你想阻止也不行。

但是如果你设置 --strictNullChecks,则 null 和 undefined ,除非你使用联合类型包含 null 和 undefined, 否则是不能赋值给其他类型的:

即这种情况下,string | null, string | undefined和 string | undefined | null 都是不同的类型

可选参数和可选属性

使用 --strictNullChecks, 可选参数会被自动加上 | undefined

同样的可选属性也是如此:

类型保护和类型断言

当代码中需要通过类型保护去除 null 时,可以通过以下方式,这和在JS中一样

以上两种情况是编译器去除的 null,但是如果编译器不能处理时(比如存在嵌套函数调用的情况时),需要手动添加类型断言去除:

以上这段代码,尽管我们知道 epither 函数不会是 null,但是编译器不知道,这时候需要手动添加类型断言:语法是添加 ! 后缀

5. 类型别名

类型别名可以给类型起一个新名字,和接口有点类似,但是可以给原始值,联合类型,元祖以及其他任何需要手写的类型进行起名:

起别名不是新建类型,只是新建名字来引用那个类型。给原始类型起别名通常没有什么用,只是作为文档时,可阅读性强一些。

同接口一样,类型别名也可以是泛型

接口和类型别名的区别

  • 接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名

  • 类型别名不能被 extends和 implements(自己也不能 extends和 implements其它类型。 因此应该尽量使用接口代替别名

  • 如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名

字符串字面量类型

字符串字面量允许指定字符串必须的固定值,可以与联合类型,类型保护和类型别名很好的配合。结合这些特性,可以实现实现类似枚举的字符串

字符串字面量类型还可以用于区分函数重载:

数字字面量类型

6. 可辨识联合

通过合并,单例类型,类型保护和类型别名来创建一个可辨识联合的高级模式

以上三个接口的 kind 作为 可辨识的特征,现在可以联合起来:

type Shape = Square | Rectangle | Circle;

使用可辨识联合:

完整性检查

多态的 this类型

7. 索引类型

索引类型查询: keyof

索引类型访问: T[K]

索引类型和字符串索引签名 keyof和 T[K]与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型,那么 keyof T会是 string。 并且 T[string]为索引签名的类型:

映射类型

即通过映射将类型改变为另一个类型,如将已知类型的每个属性变为可选的,或者变为只读的

此时我们可以定义映射类型,将类型进行转换

使用:

更复杂的映射,为每个属性添加代理

Readonly<T>Partial<T> 用处不小,因此它们与 PickRecord 一同被包含进了TypeScript的标准库里:

Pick

Record

8. 预定义的有条件类型

  • Exclude -- 从T中剔除可以赋值给U的类型。 T-U

  • Extract -- 提取T中可以赋值给U的类型。 T 与 U 的交集

  • NonNullable -- 从T中剔除null和undefined。

  • ReturnType -- 获取函数返回值类型。

  • InstanceType -- 获取构造函数类型的实例类型。

Last updated

Was this helpful?