类型守卫与类型区分
interface Bird {fly();layEggs();}interface Fish {swim();layEggs();}function getSmallPet(): Fish | Bird {const fly = () => {};const swim = () => {};const layEggs = () => {};return {fly,swim,layEggs,};}
用户自定义的类型守卫
使用类型判定
// 使用类型判定function isFish(pet: Fish | Bird): pet is Fish {return (pet as Fish).swim !== undefined;}if (isFish(pet)) {pet.swim();} else {pet.fly();}
在这个例子里,pet is Fish就是类型谓词。 谓词为parameterName is Type这种形式parameterName必须是来自于当前函数签名里的一个参数名。
注意TypeScript不仅知道在if分支里pet是Fish类型; 它还清楚在else分支里,一定_不是_Fish类型,一定是Bird类型。
使用in操作符
// 使用in操作符
function move(pet: Fish | Bird) {
if ("swim" in pet) {
return pet.swim();
}
return pet.fly();
}
typeof类型守卫
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类型守卫
instanceof类型守卫是通过构造函数来细化类型的一种方式
instanceof的右侧要求是一个构造函数,TypeScript将细化为:
- 此构造函数的prototype属性的类型,如果它的类型不为any的话
- 构造签名所返回的类型的联合
可以为null的类型
默认情况下,类型检查器认为null与undefined可以赋值给任何类型。—strictNullChecks标记可以解决此错误:当你声明一个变量时,它不会自动地包含null或undefined。 你可以使用联合类型明确的包含它们。可选参数和可选属性
使用了—strictNullChecks,可选参数会被自动地加上| undefined类型守卫和类型断言
类型守卫
类型断言function f(sn: string | null): string { return sn || "default"; }
如果编译器不能够去除null或undefined,你可以使用类型断言手动去除。 语法是添加!后缀:identifier!从identifier的类型里去除了null和undefined类型别名
起别名不会新建一个类型,它创建了一个新名字来引用那个类型。 给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
类型别名也可以是泛型 ```typescript type Container= { value: T };
type Tree
<a name="VG2sX"></a>
### 接口vs类型别名
接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。<br />如果你**无法**通过接口来描述一个类型**并且需要使用联合类型或元组类型**,这时通常会使用类型别名。
<a name="bIuH9"></a>
## 字符串字面量类型
```typescript
type Easing = "ease-in" | "ease-out" | "ease-in-out";
数字字面量类型
type NumberList = 1 | 2 | 3 | 4 | 5 | 6;
布尔字面量类型
interface ValidationSuccess {
isValid: true;
reason: null;
};
interface ValidationFailure {
isValid: false;
reason: string;
};
type ValidationResult =
| ValidationSuccess
| ValidationFailure;
枚举成员类型
可辨识联合
可以合并单例类型,联合类型,类型守卫和类型别名来创建一个叫做可辨识联合的高级模式。可辨识联合在函数式编程里很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:
- 具有普通的单例类型属性—可辨识的特征。
- 一个类型别名包含了那些类型的联合—联合。
- 此属性上的类型守卫。
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并且指定一个返回值类型:
第二种方法使用never类型,编译器用它来进行完整性检查: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; } }
这里,assertNever检查s是否为never类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case,那么s将具有一个真实的类型并且你会得到一个错误。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 } }多态的this类型
索引类型
一个常见的JavaScript模式是从对象中选取属性的子集。 ```typescript function pluck(o: T, propertyNames: K[]): T[K][] { return propertyNames.map(n => o[n]); }
interface Car { manufacturer: string; model: string; year: number; } let taxi: Car = { manufacturer: ‘Toyota’, model: ‘Camry’, year: 2014 };
// Manufacturer and model are both of type string, // so we can pluck them both into a typed string array let makeAndModel: string[] = pluck(taxi, [‘manufacturer’, ‘model’]);
// If we try to pluck model and year, we get an // array of a union type: (string | number)[] let modelYear = pluck(taxi, [‘model’, ‘year’])
<a name="ZVh4C"></a>
### 索引类型查询操作符
通过索引类型查询能够获取给定类型中的属性名类型。<br />keyof<br />对于任何类型T,keyof T的结果为T上**已知的公共属性**名的联合。
```typescript
let carProps: keyof Car; // the union of ('manufacturer' | 'model' | 'year')
索引类型查询解析
可以用来解析对象和数组,所以索引类型查询的结果类型是联合类型 string | number | symbol的子类型。
如果类型T中包含字符串索引签名,那么将string类型和number类型添加到结果类型中
interface T {
[prop:string]: number;
}
type keyofT = keyof T // string | number
如果类型T中包含数值索引签名,那么将number类型添加到结果类型中
如果类型T中包含其它属性成员,那么将表示属性名的字符串字面量类型和数字字面量类型添加到结果类型中
interface T {
0:boolean;
a:string;
b():void
}
type keyofT = keyof T // 0 | 'a' | 'b'
当对any类型使用索引类型查询时,结果类型固定为联合类型”string | number |symbol”。
联合类型
如果查询的类型为联合类型,那么先计算联合类型的结果类型,再执行索引类型查询。
交叉类型
如果查询的类型为交叉类型,那么会将原索引类型查询展开为子索引类型查询的联合,展开的规则类似于数学中的“乘法分配律”
keyof (A & B) = keyof A | keyof B
索引访问操作符
T[K]
索引访问类型能够获取对象类型中属性成员的类型。
T和K都表示类型,并且要求K类型必须能够赋值给”keyof T”类型。”T[K]”的结果类型为T中K属性的类型。
type T = {x:boolean;y:string}
type Kx = 'x'
type T0 = T[Kx] //boolean
type Ky = 'y'
type T1 = T[Ky] //string
若K是字符串字面量类型、数字字面量类型、枚举字面量类型或“unique symbol”类型,并且类型T中包含名为K的公共属性,那么“T[K]”的类型就是该属性的类型。
const s :unique symbol = Symbol();
enum E = {
A = 10
}
type T = {
0:string;
x:boolean;
[E.A]:number;
[s]:bigint
}
type TypeOfNumberLikeName = T[0] //string
type TypeOfStringLikeName = T['x'] //boolean
若K是联合类型,那么”T[K]” 等于联合类型”T[K1] | T[K2]”
若K类型能够赋值给string类型,且类型T中包含字符串索引签名,那么”T[K]”为字符串索引签名的类型。但如果类型T中包含同名的属性,那么同名属性的类型拥有更高的优先级。
interface T {
a:true;
[prop:string]:boolean;
}
type Ta = T['a'] //true
type Tb = T['b'] //boolean
若K类型能够赋值给number类型,且类型T中包含数值索引签名,那么”T[K]”为数值索引签名的类型。但如果类型T中包含同名的属性,那么同名属性的类型拥有更高的优先级。
映射类型
映射类型是typescript提供的从旧类型中创建新类型的一种方式。新类型以相同的形式去转换旧类型里的每个属性。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
最简单的映射类型
type Keys = 'options' | 'options2'
type Flags = {[K in Keys]:boolean}
type Flags = {
option1: boolean;
option2: boolean;
}
它的语法与索引签名的语法类型,内部使用了for .. in。 具有三个部分:
- 类型变量K,它会依次绑定到每个属性。
- 字符串字面量联合的Keys,它包含了要迭代的属性名的集合。
- 属性的结果类型。
映射对象类型是一个类型运算符,它能够遍历联合类型并以该联合类型的类型成员作为属性名类型来构造一个对象类型type Nullable<T> = { [P in keyof T]: T[P] | null } type Partial<T> = { [P in keyof T]?: T[P] }
in是遍历语法的关键字;K表示要遍历的类型,由于遍历的结果类型将作为对象属性名类型,因此类型K必须能够赋值给联合类型“string | number | symbol”,因为只有这些类型的值才能作为对象的键;P是类型变量,代表每次遍历出来的成员类型;T是任意类型,表示对象属性的类型,并且在类型T中允许使用类型变量P。{readonly [P in K]? T}//类型成员为字符串字面量 type MappedStringObjectType = {[P in 'x'] : boolean} // {x:boolean} //类型成员为数值字面量 type MappedNumberObjectType = {[P in 0] : boolean} //{0:boolean} //类型成员为String类型 type MappedStringIndexObjectType = {[P in string]:boolean} //{[x:string]:booleran} //类型成员为Number类型 type MappedNumberIndexObjectType = {[P in number]:boolean} //{[x:number]:booleran}同态映射对象类型
type T = {a?:string;b:number} type K = keyof T // 同态映射对象类型 type HOME = {[P in keyof T]:T[P]} //{a?:string;b:number} // 非同态映射对象类型 type MOT = {[P in K]:T[P]} // {a:string;b:number}修饰符拷贝
同态映射对象类型的一个重要性质是,新的对象类型会默认拷贝源对象类型中所有属性的readonly修饰符和“?”修饰符。改进的修饰符拷贝
为了改进映射对象类型中修饰符拷贝行为的一致性,TypeScript特殊处理了映射对象类型中索引类型为类型参数(针对泛型)的情况
此例中,Pick类型为非同态映射对象类型,因为它的语法中不包含索引类型查询。但是在Pick类型中,K不是某一具体类型,而是一个类型参数,并且存在泛型约束“K extends keyof T”。这时,TypeScript会特殊处理这种形式的映射对象类型来保留属性修饰符。type Pick<T,K extends keyof T> = { [P in K] : T[P] }添加和移除修饰符
“+”修饰符,为映射属性添加”?”修饰符或readonly修饰符。
“–”修饰符,为映射属性移除”?”修饰符或readonly修饰符。
“+”修饰符和“–”修饰符应用在”?”修饰符和readonly修饰符之前
编译器在移除属性的”?”修饰符时,同时会移除属性类型中的undefined类型,但是不会移除null类型同态映射对象类型深入
若T为原始类型,则不进行任何映射,同态映射对象类型“HMOTtype HOME<T,X> = {[P in keyof T]:X}”等于类型T
若T为联合类型,则对联合类型的每个成员类型求同态映射对象类型,并使用每个结果类型构造一个联合类型
若T为数组类型,则同态映射对象类型“HMOTtype HOME<T,X> = {[P in keyof T]:X} type T = {a:string} | {b:number}; type R = HOME<T,boolean>; // {a:boolean} | {b:boolean}”也为数组类型 type HOME<T,X> = {[P in keyof T]:X} type T = number[] type R = HOME<T,string>; // string[]条件类型
条件类型与条件表达式类似,它表示一种非固定的类型。条件类型能够根据条件判断从可选类型中选择其一作为结果类型。条件类型的定义
在该语法中,extends是关键字;T、U、X和Y均表示一种类型。若类型T能够赋值给类型U,则条件类型的结果为类型X,否则条件类型的结果为类型Y。条件类型的结果类型只可能为类型X或者类型YT extends U ? X : Y
在实际应用中,条件类型通常与类型参数结合使用。type TypeName<T> = T extends string ? 'string' : T extends number ? 'number' : 'undefined'分布式条件类型
在条件类型“T extends U ? X : Y”中,如果类型T是一个裸(Naked)类型参数,那么该条件类型也称作分布式条件类型。裸类型参数
从字面上理解,裸类型参数是指裸露在外的没有任何装饰的类型参数。如果类型参数不是复合类型的组成部分而是独立出现,那么该类型参数称作裸类型参数// 裸类型参数 type T0<T> = T extends string ? true : false; // 非裸类型参数,类型参数T不是裸类型参数,因为它是元组类型的组成部分 type T1<T> = [T] extends [string] ? true : false分布式行为
与常规条件类型相比,分布式条件类型具有一种特殊的行为,那就是在使用实际类型参数实例化分布式条件类型时,如果实际类型参数T为联合类型,那么会将分布式条件类型展开为由子条件类型构成的联合类型type T = A|B T extends U ? X : Y = (A extends U ? X : Y) | (B extends U ? X : Y)过滤联合类型
```typescript type Diff= T extends U ? never : T; // Remove types from T that are assignable to U type Filter = T extends U ? T : never; // Remove types from T that are not assignable to U
type T30 = Diff<”a” | “b” | “c” | “d”, “a” | “c” | “f”>; // “b” | “d” type T31 = Filter<”a” | “b” | “c” | “d”, “a” | “c” | “f”>; // “a” | “c”
<a name="dB7Dp"></a>
#### 避免分布式行为
一种可行的方法是将分布式条件类型中的裸类型参数修改为非裸类型参数,这可以通过将extends两侧的类型包裹在元组类型中来实现
<a name="OIlMz"></a>
### infer关键字
在extends语句中类型U的位置上允许使用infer关键字来**定义可推断的类型变量**,可推断的类型变量只允许在条件类型的true分支中引用,即类型X的位置上使用。示例如下
```typescript
T extends U ? X : Y
T extends infer U ? U : Y
type CT<T> = T extends Array<infer U> ? U : never;
type T = CT<Array<number>> //number
// 推断函数返回值类型
type ReturnType<T extends (...args:any) => any> = T extends (...args:any) => infer R ? R :any
