TypeScript 技巧集锦

原文连接:github.com/whinc/blog/…html

image

编写 TypeScript(后面简称TS)应用是一个与类型斗争的过程,你须要使用 TS 提供的类型工具经过不一样的组合来精确描述你的目标。描述越精确,类型约束和提示越准确,潜在错误越少。反之,描述越模糊(如any一把唆),TS 能提供的类型辅助就越少,潜在的错误也就越多。如何描写精确的类型描述须要掌握 TS 的基础概念,同时掌握常见技巧和类型工具,前者能够阅读官网的 TypeScript 手册 学习,后者能够经过本文学习部分,后续进阶学习就靠多实践多总结了。git

经常使用技巧

这节主要介绍一些基础的类型工具,这是全部高级类型的基石。github

typeof T - 获取变量的类型

typeof能够获取 JS 变量的类型,它是 JS 变量空间向 TS 类型空间转换的桥梁,有了它咱们能够从已有的变量中抽取类型进行进一步处理。typescript

const person = {
    name: "jim",
    age: 99
}
 
type Person = typeof person
// type Person = {
// name: string;
// age: number;
// }
复制代码

keyof T - 获取类型的键

keyof可获取目标类型的键,返回的是string | number | symbol的子类型。编程

interface Person {
    name: string
    age: number
}
 
type K = keyof Person
// type K = "name" | "age"
复制代码

进一步阅读:数组

T[K] - 类型映射,获取类型的值

配合keyof一块儿使用获取一组 key 对应的 valueapp

interface Person {
    name: string
    age: number
}
 
type V = Person[keyof Person]
// type V = "string" | "number"
复制代码

in T - 遍历类型的键

配合索引访问类型一块儿使用,用于表示目标类型的 key函数

interface Person {
    name: string
    age: number
}
 
type Partial<T> = { [P in keyof T]?: T[P]}
 
type PartialPerson = Partial<Person>
// type PartialPerson = {
// name?: string | undefined;
// age?: number | undefined;
// }
复制代码

T extends U ? X : Y - 条件类型

extends除了用在继承类时会使用,还能够用于判断一个类型是否比另外一个类型的父类型,并根据判断结果执行不一样的类型分支,其使得 TS 类型具有了必定的编程能力。工具

extends条件判断规则以下:若是T能够赋值给U返回X,不然Y,若是 TS 没法肯定T是否能够赋值给U,则返回X | Ypost

type isString<T> = T extends string ? true : false
 
type T1 = isString<number>  // false
type T2 = isString<string>  // true
复制代码

进一步阅读

infer T - 类型推断

extends条件类型的子句中,可使用infer T来捕获指定位置的类型(该类型由 TS 编译器推断),在infer后面的子句中可使用捕获的类型变量。配合extends条件类型,截取符合条件的目标的某部分类型。

type ParseInt = (n: string) => number
// 若是是类型 T 是函数,则 R 会捕获其返回值类型并返回 R,不然返回 any
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
type R = ReturnType<ParseInt>   // number
 
type GetType<T> = T extends (infer E)[] ? E : never
type E = GetType<['a', 100]>
复制代码

never

never与类型TT是除unknown外的其余任意类型)union 后结果是类型T,利用never的这个特色能够实现类型消除,例如将某个类型先转换成never,而后再与其余类型 union。

type a = string
type b = number
type c = never
type d = a | b |c
// type d = string | number
 
type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<string | number, string>   // number
复制代码

类型工具

TS 内置了一些经常使用的类型转换工具,熟练掌握这些工具类型不只能够简化类型定义,并且能够基于此构建更复杂的类型转换。

下面是 TS 内置的全部类型工具,我加了下注释和示例方便理解,你能够先只看示例,测试下可否自行写出对应的类型实现(Playground)。

/** * 使 T 的全部属性变为为可选的 * * Partial<{name: string}> // {name?: string | undefined} */
type Partial<T> = {
    [P in keyof T]?: T[P];
};
 
/** * 使类型 T 的全部属性变为必需的 * * Required<{name?: string}> // {name: string} */
type Required<T> = {
    [P in keyof T]-?: T[P];
};
 
/** * 使类型 T 的全部属性变为只读的 * * Readonly<{name: string}> // {readonly name: string} */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
 
/** * 从类型 T 中挑出全部属性名出如今类型 K 中的属性 * * Pick<{name: string, age: number}, 'age'> // {age: number} */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
 
/** * 构造一个 key-value 类型,其 key 是类型 K, value 是类型 T * * const map: Record<string, number> = {a: 1, b: 2} */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
 
/** * 从类型 T 中剔除类型 U * * Exclude<'a' | 'b', 'a'> // 'b' */
type Exclude<T, U> = T extends U ? never : T;
 
/** * 从类型 T 中挑出类型 U * * Extract<string | number, number> // number */
type Extract<T, U> = T extends U ? T : never;
 
/** * 从类型 T 中剔除全部属性名出如今类型 K 中的属性 * * Omit<{name: string, age: number}, 'age'> // {name: number} */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
 
/** * 剔除类型 T 中的 null 和 undefined 子类型 * * NonNullable<string | null | undefined> // string */
type NonNullable<T> = T extends null | undefined ? never : T;
 
/** * 获取函数的参数元组(注意是元组不是数组) * * Parameters<(name: string, age: number) => void> // [string, number] */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
 
/** * 获取构造函数的参数元组 * * class Person { constructor(name: string, age: number) { } } * ConstructorParameters<typeof Person> // [string, number] * * TS 中类有两个方面:实例面、静态面 * typeof Person 表示类的静态面类型 * Person 表示类的静态面实例,如构造函数、静态方法 * Person 也表示类实例的类型,如成员变量、成员方法 * new Person 表示类的实例 */
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
 
/** * 获取函数的返回类型 * * ReturnType<() => string> // string */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
 
/** * 获取构造函数的返回类型,即类的实例的类型 * * class Person { constructor(name: string, age: number) { } } * InstanceType<typeof Person> // Person */
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
复制代码

除了 TS 内置的类型工具外,还有一些第三方开发的类型工具,提供了更多类型转换工具,例如 ts-toolbelt —— TS 版"lodash"库。

下面是从 ts-toolbelt 挑选的部分示例,更多工具类型请查看它的官网。

import type { Object } from 'ts-toolbelt'

// 使对象的部分属性变为可选
type T1 = Object.Optional<{ a: string; b: number }, 'a'>
// type T1 = {
// a?: string | undefined;
// b: number;
// }

// 合并两个对象,前面对象为 undefined 的属性被后面对象对应属性覆盖
type T2 = Object.MergeUp<{ a: 'a1', b?: 'b1' }, { a: 'a2', b: 'b2' }>
// type T2 = {
// a: "a1";
// b: "b1" | "b2";
// }
复制代码

案例解析

掌握基础概念后,可能依然没法写出精确的类型描述,由于这些概念仅仅停留在单个概念的使用,须要进一步实践练习,才可能融会贯通。下面搜集了一些 TS 的类型转换案例(题目),能够从中学习一些解题思路和代码实现。

No.1

问题 假定对象的全部值都是数组类型,例如:

const data = {
  a: ['x', 'y', 'z'],
  b: [1, 2, 3]
} as const
复制代码

要求获取上述对象值中的数组元素的类型,例如:

type TElement = "x" | "y" | "z" | 3 | 1 | 2
复制代码

解题思路 首先拿到对象的值类型,而后经过数组下标获取数组元素的类型。

参考代码

type GetValueElementType<T extends { [key: string]: ReadonlyArray<any> }> = T[keyof T][number]
type TElement  = GetValueElementType<typeof data>
复制代码

扩展

若是对象的值不都是数组类型呢?

例以下面这样

const data = {
  a: ['x', 'y', 'z'],
  b: [1, 2, 3],
  c: 100
} as const
复制代码

解题思路:首先依然是拿到对象的值类型,而后过滤出数组类型,最后取数组的元素类型

// 实现1:经过 extends 判断对象的值类型,经过数组下标获取元素类型
type GetValueElementType<T> = { [K in keyof T]: T[K] extends ReadonlyArray<any> ? T[K][number] : never }[keyof T]
 
 
// 实现2:经过 extends 判断对象的值类型,经过 infer 推断,获取数组元素类型
type GetValueElementType<T> = { [K in keyof T]: T[K] extends ReadonlyArray<infer E> ? E : never }[keyof T]
复制代码

案例部分未完待续。。。

参考

相关文章
相关标签/搜索