常用类型
null
undefined
boolean
number
string
枚举
数组/元组
any/unknown
interface
可选属性
只读属性
接口合并
function
可选参数
默认参数
剩余参数
函数重载
泛型
泛型约束
类型别名
通过关键字 type 给类型起个别名,类型别名较多应⽤于联合类型、交叉类型这种复合类型。
type brand = string;
const str: brand = 'string';
type Tree<T, U> = {
left: T;
right: U;
};
interface VS type
类型别名看起来和接⼝⾮常类似,区别之处在于:
1、接⼝可以实现 extends 和 implements,类型别名不⾏。
2、在同⼀个命名空间下,同名的接⼝会合并,能添加新属性。
3、类型别名并不会创建新类型,是对原有类型的引⽤,⽽接⼝会定义⼀个新类型。
4、接⼝只能⽤于定义对象类型,⽽类型别名的声明⽅式除了对象之外还可以定义交叉、联合、原始类型等。
类型别名是最初 TypeScript 做类型约束的主要形式,后来引⼊接⼝之后,TypeScript 推荐我们尽可能的使⽤接⼝来规范我们的代码。
交叉类型
交叉类型 是将多个类型合并为⼀个类型。
语法
// 类型1 & 类型2
使用
interface Person {
name: string;
}
interface Clock {
time: string;
}
function foo(arg: Person & Clock) {
console.log(arg.name);
console.log(arg.time);
}
其中 arg 拥有 Person 和 Clock 的所有属性
联合类型
联合类型 表示取值为多种中的⼀种类型,⽽ 交叉类型 每次都是多个类型的合并类型。
语法
// 类型1 | 类型2
使用
interface Cat {
name: string;
fishing: () => void;
}
interface Dog {
name: string;
barking: () => void;
}
function foo(arg: Cat | Dog) {
console.log(arg.name);
}
其中 arg 只能直接取到 name (Cat 和 Dog 公有的) 属性
实际上当 arg 的类型为 Cat 的时候我们应该能调⽤ fishing ⽅法, 所以我们判断⼀下类型
function foo(arg: Cat | Dog) {
console.log(arg.name);
// Property 'fishing' does not exist on type 'Cat | Dog'.
// Property 'fishing' does not exist on type 'Dog'.ts
if (arg.fishing) {
arg.fishing;
}
}
很遗憾我们不能直接这样判断, 我们需要使⽤ 类型断⾔ 处理 arg 的类型
类型断言
TypeScript 允许你覆盖它的推断,毕竟作为开发者你⽐编译器更了解你写的代码。
语法
<类型>值(值 as 类型);
当你在使⽤ JSX 语法时,会跟标签 <> 形式的类型断⾔混淆
所以,建议统⼀使⽤ as type 这种语法来为类型断⾔。
使用
function foo(arg: Cat | Dog) {
console.log(arg.name);
if ((arg as Cat).fishing) {
(arg as Cat).fishing();
}
}
这样就能调⽤到 Cat 的⽅法了
但是这样不是很优雅, 我们应该使⽤类型保护处理
类型保护
类型保护是指缩⼩类型的范围,在⼀定的块级作⽤域内由编译器推导其类型,提示并规避不合法的操作。
typeof
通过 typeof 运算符判断变量类型
function foo(arg: number | string) {
if (typeof arg === 'string') {
console.log(arg.length);
} else {
arg.toFixed();
}
}
instanceof
instanceof 与 typeof 类似,区别在于 typeof 判断基础类型,instanceof 判断是否为某个对象
class Cat {
fishing() {}
}
class Dog {
barking() {}
}
function foo(arg: Cat | Dog) {
if (arg instanceof Cat) {
arg.fishing();
} else {
arg.barking();
}
}
in
in 操作符⽤于确定属性是否存在于某个对象上,这也是⼀种缩⼩范围的类型保护。
interface Person {
name: string;
age: number;
}
interface Dog {
name: string;
barking: () => void;
}
function foo(arg: Person | Dog) {
if ('age' in arg) {
console.log(arg.age);
} else {
arg.barking();
}
}
使⽤ 'age' in arg 判断 arg 为 Person 类型
字⾯量类型保护
为每个类型增加⼀个相同的字段, 通过该字段区分类型
interface Person {
type: 'person';
name: string;
age: number;
}
interface Dog {
type: 'dog';
name: string;
barking: () => void;
}
function foo(arg: Person | Dog) {
if (arg.type === 'person') {
console.log(arg.age);
} else {
arg.barking();
}
}
通过判断 arg.type 区分类型
用户自定义的类型保护
我们可能会将判断类型的逻辑提取为⼀个函数
// 类型判断函数
function isPerson(arg: Person | Dog) {
return arg.type === 'person';
}
function foo(arg: Person | Dog) {
if (isPerson(arg)) {
// Property 'age' does not exist on type 'Person | Dog'.
// Property 'age' does not exist on type 'Dog'.
console.log(arg.age);
} else {
// Property 'barking' does not exist on type 'Person | Dog'.
// Property 'barking' does not exist on type 'Person'.
arg.barking();
}
}
这样并没有让 ts 明⽩类型, 这样我们需要⽤到 is 关键字
is 关键字
is 关键字⼀般⽤于函数返回值类型中,判断参数是否属于某⼀类型,并根据结果返回对应的布尔类型。
is 被称为类型谓词,⽤来判断⼀个变量属于某个接⼝或类型。
语法
// 变量 is 类型
const isString = (s: unknown): s is string => typeof val === 'string';
通过 is 关键字将类型范围缩⼩为 string 类型,这也是⼀种代码健壮性的约束规范
interface Person {
type: 'person';
name: string;
age: number;
}
interface Dog {
type: 'dog';
name: string;
barking: () => void;
}
function isPerson(arg: Person | Dog): arg is Person {
return arg.type === 'person';
}
function foo(arg: Person | Dog) {
if (isPerson(arg)) {
console.log(arg.age);
} else {
arg.barking();
}
}
is 关键字经常⽤来封装”类型判断函数”,通过和函数返回值的⽐较,从⽽缩⼩参数的类型范围,所以类型谓词 is 也是⼀种类型保护。
索引类型
keyof 索引类型查询操作符
type Foo = {
a: number;
b: string;
};
interface Person {
name: string;
age: number;
}
type Bar = keyof Foo; // 'a' | 'b'
type P = keyof Person; // 'name' | 'age'
如果我们想要遍历⼀个变量的 key
const o = {
a: 1,
};
type A = keyof o;
// 'o' refers to a value, but is being used as a type here. Did you mean 'typeof o'
T[K] 索引访问操作符
interface Person {
name: string;
age: number;
}
type name = Person['name'];
⼀个对象的类型为泛型 T ,这个对象的属性类型 K 只需要满⾜ K extends keyof T ,即可得到这个属性值的类型为 T[K] 。
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
return o[name]; // o[name] is of type T[K]
}
已知参数 o 的类型为 T,参数 name 的类型 K 满⾜ K extends keyof T ,那么返回值的类型即为 T[K] 。
映射类型
映射类型可以将已知类型的每个属性都变为可选的或者只读的。
Readonly 内置类型
将⼀个类型中的属性改为只读
使用
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
实现
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
这⾥就使⽤了映射类型的语法 [K in Keys] ,来看这个语法的两个部分:
-
类型变量 K :它会依次绑定到每个属性,对应每个属性名的类型。
-
字符串字⾯量构成的联合类型的 Keys :它包含了要迭代的属性名的集合。
Partial 和 Required 内置类型
将类型中所有属性改为可选或必选
使用
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>;
type RequiredPartialPerson = Required<PartialPerson>;
实现
type Partial<T> = {
[K in keyof T]?: T[K];
};
type Required<T> = {
[K in keyof T]-?: T[K];
};
Pick 内置类型
选择⼀个类型中⼀部分的属性定义
使用
interface User {
id: number;
age: number;
name: string;
}
type PickUser = Pick<User, 'id'>;
实现
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
条件类型
条件类型 就是在初始状态并不直接确定具体类型,⽽是通过⼀定的类型运算得到最终的变量类型。
TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:
- Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
- Extract<T, U> -- 提取T中可以赋值给U的类型。
- NonNullable -- 从T中剔除null和undefined。
- ReturnType -- 获取函数返回值类型。
- InstanceType -- 获取构造函数类型的实例类型。
语法
T extends U ? X : Y
语法类似三⽬运算符:若 T 是 U 的⼦类型,则类型为 X ,否则类型为 Y 。若⽆法确定 T 是否为 U 的⼦类型,则类型为 X | Y 。
获取类型字符串
type GetTypeStr<T> = T extends number
? 'number'
: T extends string
? 'string'
: 'type';
Exclude 内置类型
排除 联合类型 中的⼀部分内容
语法
type A = 'a' | 'b' | 'c';
// 排除 c
type ExcludeC = Exclude<A, 'c'>;
实现
type Exclude<T, U> = T extends U ? never : T;
这里T extends U,是指类型T可以分配给类型U
说明:
对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用 分配律 计算最终的结果。分配律是指, 将联合类型的联合项拆成单项 ,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。
**
**
Extract 内置类型
取两个联合类型公有的部分
语法
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// 排除 a
type ExtractC = Extract<A, B>;
实现
type Extract<T, U> = T extends U ? T : never;
Omit 内置类型
得到不包含某些字段的类型
语法
type Person = {
name: string;
age: number;
addr: string;
};
type OmitPerson = Omit<Person, 'addr'>;
实现
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
infer 关键字
infer 的作⽤是让 TypeScript ⾃⼰推断,并将推断的结果存储到⼀个类型变量中, infer 只能⽤于 extends 语句中 (右边) 。
来看⼀下 ReturnType 的实现源码:
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
如果 T 满⾜约束条件 (...args: any) => any ,并且能够赋值给 (...args: any) => infer R ,则返回类型为 R ,否则为 any 类型。
模板类型文字
和 es ⾥的字符串模板很相似
type EventName<T extends string> = `${T}Changed`;
type T0 = EventName<'foo'>; // 'fooChanged'
type T1 = EventName<'foo' | 'bar' | 'baz'>; // 'fooChanged' | 'barChanged' |
'bazChanged'
type T3 = `${'top' | 'bottom'}-${'left' | 'right'}`;
// 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
使用
ReturnType 的应用
const add = (x: number, y: number) => x + y;
type t = ReturnType<typeof add>; // type t = number
获取元组第一项类型
type A = ['a', 'b', 'c'];
type ArrayZero<T extends any[]> = T extends [infer S, ...infer R] ? S : never;
type B = ArrayZero<A>;
获取字符串第⼀个字符串
type A = 'abcd';
type StrZero<T extends string> = T extends `${infer Z}${infer R}`
? Z extends string
? Z
: ''
: '';
type B = StrZero<A>;