TS从入门到放弃【十四】:高级类型(三)

303 阅读6分钟

「这是我参与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映射类型是支持用 numbersymbol 来命名属性的。

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 里面的值都是只读属性的。这就是映射类型对 numbersymbol 的支持。

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 不可以赋值给其他类型,此时它只能赋值给 unknownany 类型
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),否则返回 never
  • keyof 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”。