背景
大家用过 Typescript
都清楚,很多时候我们需要提前声明一个类型,再将类型赋予变量。
例如在业务中,我们需要渲染一个表格,往往需要定义:
interface Row { user: string email: string id: number vip: boolean // ...}const tableDatas: Row[] = []// ...复制代码
有时候我们也需要表格对应的搜索表单,需要其中一两个搜索项,如果刚接触 typescript 的同学可能会立刻这样写:
interface SearchModel { user?: string id?: number } const model: SearchModel = { user: '', id: undefined }复制代码
这样写会出现一个问题,如果后面id 类型要改成 string
,我们需要改 2 处地方,不小心的话可能就会忘了改另外一处。所以,有些人会这样写:
interface SearchModel { user?: Row['user'] id?: Row['id']} 复制代码
这固然是一个解决方法,但事实上,我们前面已经定义了 Row
类型,这其实是可以更优雅地复用的:
const model: Partial= { user: '', id: undefined }// 或者需要明确指定 key 的,可以const model2: Partial
>复制代码
这样一来,很多情况下,我们可以尽量少地写重复的类型,复用已有类型,让代码更加优雅容易维护。
上面使用到的 Partial
和 Pick
都是 typescript 内置的类型别名。下面给大家介绍一下 typescript 常用的内置类型,以及自行拓展的类型。
typescript 内置类型
Partial
将类型 T 的所有属性标记为可选属性
type Partial= { [P in keyof T]?: T[P];};复制代码
使用场景:
// 账号属性interface AccountInfo { name: string email: string age: number vip: 0|1 // 1 是vip ,0 是非vip}// 当我们需要渲染一个账号表格时,我们需要定义const accountList: AccountInfo[] = []// 但当我们需要查询过滤账号信息,需要通过表单,// 但明显我们可能并不一定需要用到所有属性进行搜索,此时可以定义const model: Partial= { name: '', vip: undefind}复制代码
Required
与 Partial 相反,Required 将类型 T 的所有属性标记为必选属性
type Required= { [P in keyof T]-?: T[P];};复制代码
Readonly
将所有属性标记为 readonly, 即不能修改
type Readonly= { readonly [P in keyof T]: T[P];};复制代码
Pick<T, K>
从 T 中过滤出属性 K
type Pick= { [P in K]: T[P];};复制代码
使用场景:
interface AccountInfo { name: string email: string age: number vip?: 0|1 // 1 是vip ,0 是非vip}type CoreInfo = Pick/* { name: string email: stirng}*/复制代码
Record<K, T>
标记对象的 key value类型
type Record= { [P in K]: T;};复制代码
使用场景:
// 定义 学号(key)-账号信息(value) 的对象const accountMap: Record= { 10001: { name: 'xx', email: 'xxxxx', // ... } }const user: Record<'name'|'email', string> = { name: '', email: ''}复制代码
// 复杂点的类型推断function mapObject(obj: Record , f: (x: T) => U): Record const names = { foo: "hello", bar: "world", baz: "bye" };// 此处推断 K, T 值为 string , U 为 numberconst lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }复制代码
Exclude<T, U>,Omit<T, K>
移除 T 中的 U 属性
type Exclude= T extends U ? never : T;复制代码
使用场景:
// 'a' | 'd'type A = Exclude<'a'|'b'|'c'|'d' ,'b'|'c'|'e' > 复制代码
乍一看好像这个没啥卵用,但是,我们通过一番操作,之后就可以得到 Pick
的反操作:
type Omit= Pick >type NonCoreInfo = Omit /*{ age: number vip: 0|1,}*/复制代码
Extract<T, U>
Exclude
的反操作,取 T,U两者的交集属性
type Extract= T extends U ? T : never;复制代码
使用 demo:
// 'b'|'c'type A = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' > 复制代码
这个看起来没啥用,实际上还真没啥卵用,应该是我才疏学浅,还没发掘到其用途。
NonNullable
排除类型 T 的 null
| undefined
属性
type NonNullable= T extends null | undefined ? never : T;复制代码
使用 demo
type A = string | number | undefined type B = NonNullable // string | numberfunction f2(x: T, y: NonNullable ) { let s1: string = x; // Error, x 可能为 undefined let s2: string = y; // Ok}复制代码
Parameters
获取一个函数的所有参数类型
// 此处使用 infer P 将参数定为待推断类型// T 符合函数特征时,返回参数类型,否则返回 nevertype Parametersany> = T extends (...args: infer P) => any ? P : never;复制代码
使用demo:
interface IFunc { (person: IPerson, count: number): boolean}type P = Parameters// [IPerson, number]const person01: P[0] = { // ...}复制代码
另一种使用场景是,快速获取未知函数的参数类型
import {somefun} from 'somelib'// 从其他库导入的一个函数,获取其参数类型type SomeFuncParams = Parameters// 内置函数// [any, number?, number?]type FillParams = Parameters 复制代码
ConstructorParameters
类似于 Parameters<T>
, ConstructorParameters 获取一个类的构造函数参数
type ConstructorParametersany> = T extends new (...args: infer P) => any ? P : never;复制代码
使用 demo:
// string | number | Date type DateConstrParams = ConstructorParameters复制代码
ReturnType
获取函数类型 T 的返回类型
type ReturnTypeany> = T extends (...args: any) => infer R ? R : any;复制代码
使用方式和 Parameters<T>
类似,不再赘述
InstanceType
获取一个类的返回类型
type InstanceTypeany> = T extends new (...args: any) => infer R ? R : any;复制代码
使用方式和 ConstructorParameters<T>
类似,不再赘述
自定义常用类型
Weaken
使用 typescript
有时候需要重写一个库提供的 interface 的某个属性,但是重写 interface
有可能会导致冲突:
interface Test { name: string say(word: string): string}interface Test2 extends Test{ name: Test['name'] | number}// error: Type 'string | number' is not assignable to type 'string'.复制代码
那么可以通过一些 type 来曲线救国实现我们的需求:
// 原理是,将 类型 T 的所有 K 属性置为 any,// 然后自定义 K 属性的类型,// 由于任何类型都可以赋予 any,所以不会产生冲突type Weaken= { [P in keyof T]: P extends K ? any : T[P];};interface Test2 extends Weaken { name: Test['name'] | number}// ok复制代码
数组 转换 成 union
有时候需要
const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const; // TS 3.4type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs']type Suit = SuitTuple[number]; // union type : 'hearts' | 'diamonds' | 'spades' | 'clubs'复制代码
根据 enum 生成 union
-
enum 的 key 值 union
enum Weekday { Mon = 1 Tue = 2 Wed = 3}type WeekdayName = keyof typeof Weekday // 'Mon' | 'Tue' | 'Wed'复制代码
-
enum 无法实现value-union , 但可以 object 的 value 值 union
const lit =
(v: V) => v;const Weekday = { MONDAY: lit(1), TUESDAY: lit(2), WEDNESDAY: lit(3)}type Weekday = (typeof Weekday)[keyof typeof Weekday] // 1|2|3复制代码
PartialRecord
前面我们讲到了 Record 类型,我们会常用到
interface Model { name: string email: string id: number age: number}// 定义表单的校验规则const validateRules: Record= { name: {required: true, trigger: `blur`}, id: {required: true, trigger: `blur`}, email: {required: true, message: `...`}, // error: Property age is missing in type...}复制代码
这里出现了一个问题,validateRules
的 key 值必须和 Model
全部匹配,缺一不可,但实际上我们的表单可能只有其中的一两项,这时候我们就需要:
type PartialRecord= Partial >const validateRules: PartialRecord = { name: {required: true, trigger: `blur`} }复制代码
这个例子组合使用了 typescript
内置的 类型别名 Partial
和 Record
。
Unpacked
解压抽离关键类型
type Unpacked= T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise ? U : T;type T0 = Unpacked ; // stringtype T1 = Unpacked ; // stringtype T2 = Unpacked<() => string>; // stringtype T3 = Unpacked >; // stringtype T4 = Unpacked []>; // Promise type T5 = Unpacked []>>; // string复制代码
总结
事实上,基于已有的类型别名,还有新推出的 infer
待推断类型,可以探索出各种各样的复杂组合玩法,这里不再多说,大家可以慢慢探索。
感谢阅读!
本文首发于
如文章对你有帮助, 是对我最大的支持
插播广告:
深圳 Shopee 长期内推 岗位:前端,后端(要转go),产品,UI,测试,安卓,IOS,运维 全都要。 薪酬福利:20K-50K?,7点下班?(划重点),免费水果?,免费晚餐?,15天年假?,14天带薪病假。 简历发邮箱:chenweiyu6909@gmail.com 或者加我微信:cwy13920,实时反馈面试进度哦。