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