切换语言为:繁体
TypeScript 内置高级类型详细介绍

TypeScript 内置高级类型详细介绍

  • 爱糖宝
  • 2024-11-25
  • 2005
  • 0
  • 0

Parameters

Parameters 用于提取函数类型的参数类型。

源码是这样的:

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


1. 参数说明

  • T:要从中提取参数类型的函数类型。

2. 条件类型 T extends (...args: infer P) => any

  • 这个条件类型用于检查 T 是否是一个函数类型。

  • (...args: infer P) => any 是一个函数签名,其中 ...args: infer P 表示函数可以接受任意数量和类型的参数,infer P 用于推断函数的参数类型。

3. 分支 ? P : never

  • 如果 T 是一个函数类型,并且成功推断出参数类型 P,则返回 P

  • 如果 T 不是一个函数类型,或者无法推断出参数类型,则返回 never

类型参数 T 为待处理的类型,通过 extends 约束为函数,参数和返回值任意。

通过 extends 匹配一个模式类型,提取参数的类型到 infer 声明的局部变量 P 中返回。

这样就实现了函数参数类型的提取:

function add(a: number, b: number): number {
        return a + b; 
        } 
type AddParameters = Parameters<typeof add>; // [number, number]


ReturnType

ReturnType 用于提取函数类型的返回值类型。

源码是这样的:

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


1. 参数说明

  • T:要从中提取返回类型的函数类型。

2. 条件类型 T extends (...args: any) => infer R

  • 这个条件类型用于检查 T 是否是一个函数类型。

  • (...args: any) => infer R 是一个函数签名,其中 ...args: any 表示函数可以接受任意数量和类型的参数,infer R 用于推断函数的返回类型。

3. 分支 ? R : any

  • 如果 T 是一个函数类型,并且成功推断出返回类型 R,则返回 R

  • 如果 T 不是一个函数类型,或者无法推断出返回类型,则返回 any

类型参数 T 为待处理的类型,通过 extends 约束为函数类型,参数和返回值任意。

用 T 匹配一个模式类型,提取返回值的类型到 infer 声明的局部变量 R 里返回。

这样就实现了函数返回值类型的提取:

function add(a: number, b: number): number {
    return a + b;
}

type AddReturnType = ReturnType<typeof add>; // number


  • typeof add 是 function (a: number, b: number): number

  • ReturnType<typeof add> 成功推断出返回类型 number

Partial

索引类型可以通过映射类型的语法做修改,比如把索引变为可选。

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


1. 参数说明

  • T:要处理的对象类型。

2. 映射类型 [P in keyof T]

  • keyof T 获取 T 类型的所有键的联合类型。

  • [P in keyof T] 是一个映射类型,遍历 T 的所有键 P

3. 可选属性 ?

  • T[P]? 表示将 T 的每个属性 P 变为可选属性。

类型参数 T 为待处理的类型。

通过映射类型的语法构造一个新的索引类型返回,索引 P 是来源于之前的 T 类型的索引,也就是 P in keyof T,索引值的类型也是之前的,也就是 T[P]。

这样就实现了把索引类型的索引变为可选的效果:

interface User {
    id: number;
    profile: {
        name: string;
        email: string;
    };
}

type PartialUser = Partial<User>;


  • User 类型的属性 id 和 profile 都是必需的。

  • Partial<User> 将这些属性变为可选的,因此 PartialUser 的类型是:

    {
        id?: number;
        profile?: {
            name: string;
            email: string;
        };
    }


    Required

可以把索引变为可选,也同样可以去掉可选,也就是 Required 类型:

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


1. 参数说明

  • T:要处理的对象类型。

2. 映射类型 [P in keyof T]-?

  • keyof T 获取 T 类型的所有键的联合类型。

  • [P in keyof T]-? 是一个映射类型,遍历 T 的所有键 P,并将每个属性 P 的可选性移除(即变为必需)。

3. 必需属性 -?

  • T[P]-? 表示将 T 的每个属性 P 的可选性移除,使其成为必需属性。

类型参数 T 为待处理的类型。

通过映射类型的语法构造一个新的索引类型,索引取自之前的索引,也就是 P in keyof T,但是要去掉可选,也就是 -?,值的类型也是之前的,就是 T[P]。

这样就实现了去掉可选修饰的目的:

interface Person {
    name?: string;
    age?: number;
    address: string;
}

type RequiredPerson = Required<Person>;


  • Person 类型的属性 name 和 age 是可选的,而 address 是必需的。

  • Required<Person> 将所有属性变为必需的,因此 RequiredPerson 的类型是:

    {
        name: string;
        age: number;
        address: string;
    }


    Readonly

同样的方式,也可以添加 readonly 的修饰:

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


类型参数 T 为待处理的类型。

通过映射类型的语法构造一个新的索引类型返回,索引和值的类型都是之前的,也就是 P in keyof T 和 T[P],但是要加上 readonly 的修饰。

Pick

映射类型的语法用于构造新的索引类型,在构造的过程中可以对索引和值做一些修改或过滤。

比如可以用 Pick 实现过滤:

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


1. 参数说明

  • T:要从中选择属性的对象类型。

  • K:要选择的属性键的联合类型,必须是 T 的键的子集(即 K 必须是 keyof T 的一部分)。

2. 映射类型 [P in K]

  • keyof T 获取 T 类型的所有键的联合类型。

  • [P in K] 是一个映射类型,遍历 K 中的所有键 P

3. 属性映射 T[P]

  • T[P] 表示从 T 类型中选择键 P 对应的属性类型。

类型参数 T 为待处理的类型,类型参数 K 为要过滤出的索引,通过 extends 约束为只能是 T 的索引的子集。

构造新的索引类型返回,索引取自 K,也就是 P in K,值则是它对应的原来的值,也就是 T[P]。

这样就实现了过滤的目的:

interface Person {
    name: string;
    age: number;
    address: string;
}

type PersonNameAndAge = Pick<Person, 'name' | 'age'>;


  • Person 类型的属性 nameage 和 address 都是必需的。

  • Pick<Person, 'name' | 'age'> 选择 name 和 age 属性,因此 PersonNameAndAge 的类型是:

    {
        name: string;
        age: number;
    }


Record

Record 用于创建索引类型,传入 key 和值的类型:

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


1. 参数说明

  • K:键的联合类型,必须是 keyof any 的子集,即 K 必须是有效的键名。

  • T:每个键对应的值类型。

2. 映射类型 [P in K]

  • [P in K] 是一个映射类型,遍历 K 中的所有键 P

3. 属性映射 T

  • T 表示每个键 P 对应的值类型。

type UserStatus = 'active' | 'inactive' | 'pending';

type UserStatusCount = Record<UserStatus, number>;

const userStatusCount: UserStatusCount = {
    active: 10,
    inactive: 5,
    pending: 3
};


  • UserStatusCount 类型的键是 'active''inactive' 和 'pending',每个键的值类型是 number

  • 因此,userStatusCount 变量必须包含这三个键,并且每个键的值必须是数字。

Exclude

当想从一个联合类型中去掉一部分类型时,可以用 Exclude 类型:

type Exclude<T, U> = T extends U ? never : T;


1. 参数说明

  • T:源类型,从中排除某些部分。

  • U:要排除的类型。

2. 条件类型 T extends U ? never : T

  • 这个条件类型用于检查 T 是否是 U 的子类型。

  • 如果 T 是 U 的子类型,则返回 never,表示从 T 中排除这部分。

  • 如果 T 不是 U 的子类型,则返回 T,表示保留这部分。

联合类型当作为类型参数出现在条件类型左边时,会被分散成单个类型传入,这叫做分布式条件类型。

所以写法上可以简化, T extends U 就是对每个类型的判断。

过滤掉 U 类型,剩下的类型组成联合类型。也就是取差集。

type AllTypes = string | number | boolean | null | undefined;
type ExcludeNullish = Exclude<AllTypes, null | undefined>;

// ExcludeNullish 的类型是 string | number | boolean


  • AllTypes 是一个联合类型,包含 stringnumberbooleannull 和 undefined

  • Exclude<AllTypes, null | undefined> 排除了 null 和 undefined,因此 ExcludeNullish 的类型是 string | number | boolean

Extract

可以过滤掉,自然也可以保留,Exclude 反过来就是 Extract,也就是取交集:

type Extract<T, U> = T extends U ? T : never;


1. 参数说明

  • T:源类型,从中提取某些部分。

  • U:要提取的类型。

2. 条件类型 T extends U ? T : never

  • 这个条件类型用于检查 T 是否是 U 的子类型。

  • 如果 T 是 U 的子类型,则返回 T,表示保留这部分。

  • 如果 T 不是 U 的子类型,则返回 never,表示排除这部分。

示例

基本示例

type AllTypes = string | number | boolean | null | undefined;
type ExtractStrings = Extract<AllTypes, string>;

// ExtractStrings 的类型是 string


  • AllTypes 是一个联合类型,包含 stringnumberbooleannull 和 undefined

  • Extract<AllTypes, string> 提取了 string 类型,因此 ExtractStrings 的类型是 string

Omit

我们知道了 Pick 可以取出索引类型的一部分索引构造成新的索引类型,那反过来就是去掉这部分索引构造成新的索引类型。

可以结合 Exclude 来轻松实现:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;


1. 参数说明

  • T:要从中排除属性的对象类型。

  • K:要排除的属性键的联合类型,必须是 keyof any 的子集,即 K 必须是有效的键名。

2. 组合使用 Pick 和 Exclude

  • keyof T 获取 T 类型的所有键的联合类型。

  • Exclude<keyof T, K> 从 keyof T 中排除 K 中的键。

  • Pick<T, Exclude<keyof T, K>> 从 T 类型中选择排除后的键。

类型参数 T 为待处理的类型,类型参数 K 为索引允许的类型(string | number | symbol 或者 string)。

通过 Pick 取出一部分索引构造成新的索引类型,这里用 Exclude 把 K 对应的索引去掉,把剩下的索引保留。

这样就实现了删除一部分索引的目的:

interface Person {
    name: string;
    age: number;
    address: string;
    email: string;
}

type PersonWithoutEmail = Omit<Person, 'email'>;

const person: PersonWithoutEmail = {
    name: 'John Doe',
    age: 30,
    address: '123 Main St'
};


  • Person 类型的属性 nameageaddress 和 email 都是必需的。

  • Omit<Person, 'email'> 排除了 email 属性,因此 PersonWithoutEmail 的类型是:

    {
        name: string;
        age: number;
        address: string;
    }


    Awaited

在递归那节我们写过取 Promise 的 ValuType 的高级类型,这个比较常用,ts 也给内置了,就是 Awaited。

它的实现比我们当时写的完善一些:

type Awaited<T> =
    T extends null | undefined
        ? T 
        : T extends object & { then(onfulfilled: infer F): any }
            ? F extends ((value: infer V, ...args: any) => any)
                ? Awaited<V>
                : never 
            : T;


1. 参数说明

  • T:要从中提取最终值的类型。

2. 条件类型 T extends null | undefined

  • 检查 T 是否是 null 或 undefined

  • 如果是,则直接返回 T

3. 条件类型 T extends object & { then(onfulfilled: infer F): any }

  • 检查 T 是否是一个对象,并且具有 then 方法。

  • then 方法的参数类型被推断为 F

4. 条件类型 F extends ((value: infer V, ...args: any) => any)

  • 检查 F 是否是一个函数,并且其第一个参数类型被推断为 V

5. 递归调用 Awaited<V>

  • 如果 F 是一个函数,并且其第一个参数类型为 V,则递归调用 Awaited<V>,继续解析 V

6. 默认返回 T

  • 如果 T 不是 null 或 undefined,且不具有 then 方法,则直接返回 T

类型参数 T 是待处理的类型。

如果 T 是 null 或者 undefined,就返回 T。

如果 T 是对象并且有 then 方法,那就提取 then 的参数,也就是 onfulfilled 函数的类型到 infer 声明的局部变量 F。

继续提取 onfullfilled 函数类型的第一个参数的类型,也就是 Promise 返回的值的类型到 infer 声明的局部变量 V。

递归的处理提取出来的 V,直到不再满足上面的条件。

这样就实现了取出嵌套 Promise 的值的类型的目的:

type Result2 = Awaited<Promise<Promise<number>>>; // number


NonNullable

NonNullable 就是用于判断是否为非空类型,也就是不是 null 或者 undefined 的类型的,实现比较简单:

type NonNullable<T> = T extends null | undefined ? never : T;


1. 参数说明

  • T:要从中排除 null 和 undefined 的类型。

2. 条件类型 T extends null | undefined ? never : T

  • 这个条件类型用于检查 T 是否是 null 或 undefined

  • 如果 T 是 null 或 undefined,则返回 never,表示从 T 中排除这部分。

  • 如果 T 不是 null 或 undefined,则返回 T,表示保留这部分。

示例

基本示例

type NullableTypes = string | number | null | undefined;
type NonNullableTypes = NonNullable<NullableTypes>;

// NonNullableTypes 的类型是 string | number


  • NullableTypes 是一个联合类型,包含 stringnumbernull 和 undefined

  • NonNullable<NullableTypes> 排除了 null 和 undefined,因此 NonNullableTypes 的类型是 string | number

Uppercase、Lowercase、Capitalize、Uncapitalize

这四个类型是分别实现大写、小写、首字母大写、去掉首字母大写的。

它们的源码时这样的:

type Uppercase<S extends string> = intrinsic;

type Lowercase<S extends string> = intrinsic;

type Capitalize<S extends string> = intrinsic;

type Uncapitalize<S extends string> = intrinsic;


啥情况,intrinsic 是啥?

这个 intrinsic 是固有的意思,就像 js 里面的有的方法打印会显示 [native code] 一样。这部分类型不是在 ts 里实现的,而是编译过程中由 js 实现的。

TypeScript 内置高级类型详细介绍

其实就是 ts 编译器处理到这几个类型时就直接用 js 给算出来了。

为啥要这样做呢?

因为快啊,解析类型是要处理 AST 的,性能比较差,用 js 直接给算出来那多快呀。

总结

虽然我们学完 6 个套路,各种类型编程逻辑都能写了,但是常用的类型 TS 已经内置了。

这些内置的高级类型用我们学的套路很容易可以实现。

比如用模式匹配可以实现:Parameters、ReturnType

用模式匹配 + 重新构造可以实现:OmitThisParameter

用重新构造可以实现:Partial、Required、Readonly、Pick、Record

用模式匹配 + 递归可以实现: Awaited

用联合类型在分布式条件类型的特性可以实现: Exclude

此外还有 NonNullable 和四个编译器内部实现的类型:Uppercase、Lowercase、Capitalize、Uncapitalize。

这些类型也不咋需要记,就算忘记了自己也能很快的实现。重点还是放在 6 个类型编程的套路上。

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.