TS部分特性和内置类型(持续更新)

基础

extends 关键词

extends有三种用途:继承、泛型约束、分配(条件类型)

1. 继承/扩展

一方面和 js 作用一致,用在 class 上。

另一方面,也可以继承类型

 interface Person { name: string }
 
 interface Child extends Person { age: number }
 // interface Child = { name: string; age: number }
2. 泛型约束

在泛型中,有时需要约束类型参数,可以借助extends来完成,比如

// 希望传入的参数是个函数
type Type<T extends (props: any) => void>= { info: T };
const obj: Type<() => void> = {info: function(){}} 

// obj中的person属性必须包含name属性
interface Type<T extends { name: string }> {
  person: T;
}
const obj: Type<{ name: string; age: number }> = {
  person: { name: 'hxy', age: 25 },
};
3. 分配(条件类型)

extends还可以用来判断一个类型是否可以分配给另一个类型,先看例子

type A = { name: string };
type B = { name: string };
type C = Child extends Person ? string : number;
// type C = string

和三元运算一样,第三行的意思是,A类型 是否可以分配给 B类型?是的话 C 为string,否则 C 为number。这里的 A 和 B 类型一致,当然可以分配。

这里就可以明白上面第二条的泛型约束 T1 extends T2,意思就是 T1 需要可以分配给 T2。

需要强调的是,**A 可以分配给 B,不等于 A 是 B 的子集。**再看例子

type A = { name: string; age: number };
type B = { name: string };
type C = A extends B ? string : number; 
// type C = string

这里的 A 显然不是 B 的子集,但条件判断依然成立(依然可以分配),可以理解为如果 A 是在 B 上扩展了一些属性,比如class A extends B {},那么 A 就可以分配给 B。如果这里把 A 的 name 属性变为可选属性,那当然就不可分配。

有了 extends 可以用作分配的基础知识,下面来详细看看[条件类型](# 条件类型)

条件类型

从[上文](#3. 分配(条件类型) )明白条件类型的基本用法,下面看一个搭配泛型使用的例子

interface IdLabel {
  id: number;
}
interface NameLabel {
  name: string;
}
 
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

这里是函数重载的写法,描述了 createLabel 是如何基于输入值的类型不同而做出不同的决策,返回不同的类型。可以看到这里只有两个类型,就需要重载三次,如果有一天要加一个类型,重载的次数会呈指数增加。

写js代码时我们通常会使用三元运算符减少一些if/switch语句,上面的例子同样可以用条件类型优化

type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

此时可以通过这个条件类型简化函数重载

function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  throw "unimplemented";
}
 
let a = createLabel("typescript");
// let a: NameLabel
 
let b = createLabel(2.8);
// let b: IdLabel
 
let c = createLabel(Math.random() ? "hello" : 42);
// let c: NameLabel | IdLabel
分发条件类型
type Example1 = 'x' extends 'x' ? string[] : number[];
// type Example1 = string[]
type Example2 = 'x' | 'y' extends 'x' ? string[] : number[];
//type Example2 = number[]

type ToArray<T> = T extends 'x' ? string[] : number[];
type Example3 = ToArray<'x' | 'y'>; // ?

直觉告诉我Example3number[],实际上是string[] | number[],因为若给 ToArray 传入一个联合类型,这个条件类型会被分发到联合类型的每个成员

ToArray<'x' | 'y'>;
// 相当于
ToArray<'x'> | ToArray<'y'>;
// 结果显然就是 string[] | number[]

通常这是我们期望的行为,如果要避免这种行为,可以用方括号包裹 extends 关键字的每一部分。

type ToArray<T> = [T] extends ['x'] ? string[] : number[];
type Example3 = ToArray<'x' | 'y'>;
// type Example3 = number[];

infer 关键词

条件类型的基本语法 T extends U ? X : Y;

如果占位符类型 U 是一个可以被分解成几个部分的类型,比如数组类型、元组类型、函数类型、字符串字面量类型等。这时候可以通过 infer 来推断 U 类型中某个部分的类型。

infer使用限制:只能被用在条件类型中,只能在true分支使用。

// 1. 推断数组(或者元组)的类型
type ArrayItem<T> = T extends (infer U)[] ? U : never;
type Example1 = Type1<string[]>
// type Example1 = string
type Example2 = ArrayItem<[number, string]>
// type Example2 = string | number

// 2. 推断数组(或者元组)第一个元素的类型(最后一个写法类似)
type First<T extends unknown[]> = T extends [infer P, ...infer _] ? P : never
type Example3 = First<[3, 2, 1]>
// type Example3 = 3

// 3. 推断函数类型的参数 和 推断函数类型的返回值,内置了Parameters 和 ReturnType

readonly 关键词

可以在数组、元祖、接口、对象类型上使用readonly。

类里的字段加一个 readonly 前缀修饰符,会阻止在构造函数之外的赋值。

as const也会把字段转为只读,一般用在数组和对象上。

type T1 = readonly [number, string] // 元祖
type T2 = readonly string[] // 数组
type T3 = {readonly name: string}
type T4 = readonly string // ❌ 不允许这样写

class Person {
  readonly name: string = "hxy";

  // 只允许在constructor里修改
  constructor(otherName?: string) {
    if (otherName !== undefined) {
      this.name = otherName;
    }
  }
}

const arr = [1,2] as const
// const arr: readonly [1, 2]
const arr = {name: 'hxy'} as const
// const arr: { readonly name: "hxy"; }

内置类型

Partial

将 T 中所有属性变为可选的

type Partial<T> = { [P in keyof T]?: T[P] };

Readonly

将 T 中所有属性变为只读的

type Readonly<T> = { readonly [P in keyof T]: T[P] };

Pick

从已有对象类型中选取给定的属性及其类型,然后构建一个新的对象类型。

T 表示源对象类型,类型参数 K 提供了待选取的属性名类型,它必须为类型 T 中存在的属性。

type Pick<T, K extends keyof T> = { [P in K]: T[P] };

// 例
type T1 = { name: string; age: number; gender: string };
type T2 = Pick<T1, 'age'|'name'>
// type T1 = { age: number; name: string; }

Record

构建一个新的对象类型,键的类型是 K (in K),值的类型是 T

type Record<K extends keyof any, T> = { [P in K]: T };

// 例
type keys = 'A' | 'B'
type T = Record<keys, number>
// type T = { A: number; B: number; }

ReturnType

获取函数的返回类型,借助条件类型和infer

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

// 例
const add = (num1: number, num2: number) => num1 + num2;
type returnType = ReturnType<typeof add>;
// type returnType = number

Parameters

获取函数的参数的类型,返回的一定是元祖或数组类型。借助条件类型和infer

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

// 例
const add = (...args: number[]) => args.reduce((p, c) => p + c);
type Para = Parameters<typeof add>;
// type Para = number[]
const a: Para = [1, 2];

你可能感兴趣的:(前端,typescript)