「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。
1、增加或移除修饰符
我们之前通过映射,遍历一个类型的属性,给每一个属性添加 readonly 或者 可选 修饰符。
可以通过 + 或者 -,作为前缀来指定增加还是删除修饰符。
type ReadonlyInfo = {
readonly age: number;
readonly name: string;
readonly sex: string;
}
type RemoveReadonly<T> = {
-readonly [P in keyof T]: T[P]
};
type InfoWithoutReadonly = RemoveReadonly<ReadonlyInfo>;
ReadonlyInfo中的参数都是只读的。我们定义新的类型 InfoWithoutReadonly,此类型通过 RemoveReadonly 作用后,将入参的全部只读修饰符去掉。
同理,可以把可选属性去掉:
type RemoveSelect<T> = {
[P in keyof T]-?: T[P]
};
2、keyof 和映射类型
keyof 和 映射类型是支持用 number 和 symbol 来命名属性的。
const stringIndex = 'a';
const numberIndex = 1;
const symbolIndex = Symbol();
type Objs = {
[stringIndex]: string,
[numberIndex]: number,
[symbolIndex]: symbol
};
type keysType = keyof Objs; // type keysType = "a" | 1 | typeof symbolIndex
新类型 keysType 是由 Objs 的 key 组成的,它是一个联合类型("a" | 1 | typeof symbolIndex)。可以看到是支持 number 类型和 symbol 类型的。
再来看一个映射类型的例子
type ReadonlyTypes<T> = {
readonly [P in keyof T]: T[P]
};
type Objs = {
[stringIndex]: string,
[numberIndex]: number,
[symbolIndex]: symbol
};
let objs: ReadonlyTypes<Objs> = {
a: 'aa',
1: 11,
[symbolIndex]: Symbol()
};
objs[1] = 'b'; // Error:无法分配到 "[numberIndex]" ,因为它是只读属性。
可以看到 objs 里面的值都是只读属性的。这就是映射类型对 number 和 symbol 的支持。
3、元组和数组的映射类型
元组和数组的映射类型会生成新的元组和数组,并不会创建一个新的类型。
// 将对象中的所有值都封装成 Promise 类型
type MapToPromise<T> = {
[K in keyof T]: Promise<T[K]>
}
type Tuple = [number, string, boolean];
type promiseTuple = MapToPromise<Tuple>;
let tuple1: promiseTuple = [
new Promise((resolve) => resolve(1)),
new Promise((resolve) => resolve('a')),
new Promise((resolve) => resolve(false)),
];
tuple1中的Promise 返回值(resolve),类型必须是按顺序的: number、string、boolean,这三个。对应 Tuple 中的类型。
4、unknown
unknown 是一个顶级类型,它相对于 any 来说是安全的。
- 任何类型都可以赋值给
unknown类型
let value1: unknown;
value1 = 'a';
value1 = 1;
- 如果没有类型断言或基于控制流的类型细化时,
unknown不可以赋值给其他类型,此时它只能赋值给unknown和any类型
let value2: unknown;
let value3: string = value2; // Error:不能将类型“unknown”分配给类型“string”
value1 = value2; // 我们刚才设置的 value1 是 unknow 类型
- 如果没有类型断言或基于控制流的类型细化,不能在它上面进行任何操作
let value4: unknown;
value4 += 1; // Error:运算符“+=”不能应用于类型“unknown”和“1”。
unknown与任何类型组成的交叉类型,最后都等于其他类型
type type1 = string & unknown; // type type1 = string
type type2 = number & unknown; // type type2 = number
type type3 = string[] & unknown; // type type3 = string[]
type type4 = unknown & unknown; // type type4 = unknown;
unknown与任何类型组成的联合类型(除了any),都等于unknown类型
type type5 = unknown | string; // type type5 = unknown
type type6 = unknown | any; // type type6 = any
never类型是unknown的子类型
type type7 = never extends unknown ? true : false; // type type7 = true
keyof unknown等于类型never
type type8 = keyof unknown; // type type8 = never
- 只能对
unknown进行等或不等操作,不能进行其他操作
let value1: unknown;
let value2: unknown;
(value1 === value2) ? value1 = true : value1 = false;
(value1 !== value2) ? value1 = true : value1 = false;
value1 += value2; // Error:运算符“+=”不能应用于类型“unknown”和“unknown”。
unknown类型的值不能访问它的属性,也不能作为函数调用和作为类创建实例
let value10: unknow;
value10.age; // Error:对象的类型为 "unknown"。
value10(); // Error:对象的类型为 "unknown"。
new value10(); // Error:对象的类型为 "unknown"。
- 使用映射类型时,如果遍历的是
unknown类型,则不会映射任何属性
type Types<T> = {
[P in keyof T]: number
};
type type11 = Types1<any>; // type type11 = { [x: string]: number; }
type type12 = Types1<unknown>; // type type12 = {}
5、条件类型
条件类型从语法上看像一个三元操作符。它会以一个条件表达式进行类型关系的检测,然后再后面两种类型中选择一个。
例如:T extends U ? X : Y
type Types2<T> = T extends string ? string : number;
let index1: Types2<'a'>; // let index1: string
let index2: Types2<[]>; // let index2: number
6、分布式条件类型
当待检测的类型是一个联合类型时,那该条件类型就是一个分布式条件类型,在实例化的时候,TS会自动的分化成联合类型。
type TypeName<T> = T extends any ? T : never;
type Types3 = TypeName<string | number>; // type Types3 = string | number
type TypeName1<T> =
T extends string ? string :
T extends number ? number :
T extends boolean ? boolean :
T extends undefined ? undefined :
T extends () => void ? () => void :
object;
type Types4 = TypeName1<() => void>; // type Types4 = () => void
type Types5 = TypeName1<string[]>; // type Types5 = object
type Types6 = TypeName1<string[] | (() => void) | string>; // type Types6 = string | object | (() => void)
相当于对传入的类型做了一层筛选(处理)。
我们接下来看一个分布式条件类型的实际应用:
type Diff<T, U> = T extends U ? never : T;
type Test2 = Diff<string | number | boolean, undefined | number>; // type Test2 = string | boolean
这个使用方式在 TS 中很常见,所以在TS中已经内置了:Exclude
type Test2 = Exclude<string | number | boolean, undefined | number>; // type Test2 = string | boolean
可以看到,和上面 Diff 的效果是一样的。
条件类型和映射类型结合
type Types7<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];
[K in keyof T]:遍历 T 的所有属性名T[K] extends Function ? K : never:如果属性值(T[K])是函数类型的话,就返回属性名(K),否则返回neverkeyof T:获取 T 的属性名[keyof T]:索引访问类型([ ]),里面的值是索引类型。获取不为never的类型
再来定义一个接口:
interface Part {
id: number;
name: string;
subparts: Part[];
undatePart(newName: string): void;
}
type Test1 = Types7<Part>; // type Test1 = "undatePart"
接口 Part 有四个字段,其中 undatePart 是一个函数(也就是 Function 类型),
7、条件类型的类型推断:infer
infer:用来推断类型。
我们来看一个例子:
- 定义一个条件类型
- 如果传入的是一个数组,则返回它元素的类型
- 如果是一个普通类型,则直接返回这个类型
不使用 info 的写法:
type Type8<T> = T extends any[] ? T[number] : T;
type Test3 = Type8<string[]>; // 因为是数组,所以返回其对应值的类型:type Test3 = string
T[number]可以返回值的类型。个人理解是数组中通过 number 访问,相当于通过下标访问,即可以访问到数组的内容。
使用 info 的写法:
type Type9<T> = T extends Array<infer U> ? U : T;
type Test5 = Type9<string[]>; // type Test5 = string
infer 可以推断出 传入数组的类型变量是什么,并且记录在 U 中。
8、Exclude<T, U>
从前面类型中选出不在后面类型的
type Type10 = Exclude<'a' | 'b' | 'c', 'a' | 'b'>; // type Type10 = "c"
9、Extract<T, U>
选取T中可以赋值给U的类型
type Type11 = Extract<'a' | 'b' | 'c', 'b' | 'c'>; // type Type11 = "b" | "c"
10、NonNullable<T>
从T中去掉 null 和 undefined
type Type12 = NonNullable<string | number | null | undefined>; // type Type12 = string | number
11、ReturnType<T>
获取函数类型返回值类型
type Type13 = ReturnType<() => string>; // type Type13 = string
12、InstanceType<T>
获取构造函数的实例类型(T extends 构造函数类型)
class AClass {
constructor() {}
}
type T1 = InstanceType<typeof AClass>; // type T1 = AClass:这里AClass是一个值,不是一个类型。我们通过 typeof 获取它的类型。
type T2 = InstanceType<any>; // type T2 = any
// type T3 = InstanceType<string>; // Error:类型“string”不满足约束“abstract new (...args: any) => any”。