高级类型
交叉类型
把多个类型合并成一个大而全的类型, 运算符为 &
interface obj1 {
name: string;
age: number;
}
interface obj2 {
addr: string,
fav: string[]
}
type obj = obj1 & obj2
/**
* {
* name: string
* age: number
* addr: string
* fav: string[]
* }
*/
联合类型
多个类型的"或"关系, 表示一个值可以是几种类型之一, 运算符为 |
type UnionType = string | number
const num: UnionType = 123 // OK
const str: UnionType = 'I am string' // OK
类型保护
why: 联合类型带了一个问题, 即我们只能访问此联合类型的所有类型里共有的成员
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
这很好理解, 因为不知道用户调用时会用到具体哪个类型,所以规定只能使用共同成员.
你可能会说我们可以用 if
条件判断啊. 是的,可以!
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();
}
既然有了 if
的类型判断, else
里面是不是可以省略了? 这就用到类型保护.
自定义类型保护
类型保护就是一些表达式, 它们会在运行时检查以确保在某个作用域里的类型. 要定义一个类型保护, 我们只要简单地定义一个函数, 的返回值是一个 类型谓词:
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
在这个例子里, pet is Fish
就是类型谓词. 谓词为 parameterName is Type
这种形式, parameterName
必须是来自于当前函数签名里的一个参数名.
// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
typeof
类型保护
类型保护虽然香, 但要多写一个判断函数太费力了, 况且我们平时用到的联合类型大多是基本数据类型. 幸运的是, 现在我们不必将 typeof x === "number"
抽象成一个函数, 因为TypeScript
可以将它识别为一个类型保护.
function getLength(value: string | number): number {
if (typeof value === 'string') return value.length
else return value.toString().length
}
这些 typeof
类型保护只有两种形式能被识别:
typeof v === "typename"
typeof v !== "typename"
typename
必须是
number
string
boolean
symbol
但是TypeScript
并不会阻止你与其它字符串比较, 语言不会把那些表达式识别为类型保护.
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'
}
null
类型保护
由于可以为 null
的类型是通过联合类型实现, 那么你需要使用类型保护来去除 null
. 幸运地是这与在JavaScript
里写的代码一致:
function f(sn: string | null): string {
if (sn == null) {
return "default";
} else {
return sn;
}
}
这里很明显地去除了 null
, 你也可以使用短路运算符:
function f(sn: string | null): string {
return sn || "default";
}
如果编译器不能够去除 null
或 undefined
, 你可以使用类型断言手动去除. 语法是添加 !
后缀: identifier!
从 identifier
的类型里去除了 null
和 undefined
:
// ts报错
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");
}
枚举成员类型
当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的.
索引类型
通过 索引类型查询 和 索引访问操作符
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'Jarid',
age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, string[]
keyof T
索引类型查询操作符
对于任何类型 T
, keyof T
的结果为 T
上已知的公共属性名的联合. 例如:
let personProps: keyof Person; // 'name' | 'age'
T[K]
索引访问操作符
这里, 类型语法反映了表达式语法. 这意味着 person['name']
具有类型 Person['name']
— 在我们的例子里则为 string
类型. 然而, 就像索引类型查询一样, 你可以在普通的上下文里使用 T[K]
, 这正是它的强大所在. 你只要确保类型变量 K extends keyof T
就可以了. 例如下面 getProperty
函数的例子:
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
return o[name]; // o[name] is of type T[K]
}
getProperty
里的 o: T
和 name: K
, 意味着 o[name]: T[K]
. 当你返回 T[K]
的结果, 编译器会实例化键的真实类型, 因此 getProperty
的返回值类型会随着你需要的属性改变。
let name: string = getProperty(person, 'name');
let age: number = getProperty(person, 'age');
let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age'
索引类型和字符串索引签名
keyof
和 T[K]
与字符串索引签名进行交互. 如果你有一个带有字符串索引签名的类型, 那么 keyof T
会是 string
. 并且 T[string]
为索引签名的类型:
interface Map<T> {
[key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number
类型映射
见之前总结的 "内置类型", 是同一个概念