作者: 徐海强
ts 内置类型
Partial
将其变为可选
type Partial<T> = {[P in keyof T]?: T[P];};
这里稍微解释一下
keyof T 拿到 T 所有属性名, 然后 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值.
例子:
interface People {name: string}Partial<People> => {name?: string}
其是有局限性的,只能处理一层
interface People {name: string;person: {name: string; name1: string}}type NewPeople = Partial<People>const jack: NewPeople = {name: 'jack',person: {name: 'son'}}type PowerPartial<T> = {[U in keyof T]?: T[U] extends object? PowerPartial<T[U]>: T[U]};
Readonly
只读
type Readonly<T> {readonly [P in keyof T]: T[P]}
当然这也只能一层 如上面 Partial 例子来看 jack.person.name 是可以直接修改的。 也可以和 Partial 结合起来
type ReadonlyPartial<T> = { readonly [P in keyof T]?: T[P] };
Required
type Required<T> = {[P in keyof T]-?: T[P];};
上面的-?, 这里很好理解就是将可选项代表的 ? 去掉, 从而让这个类型变成必选项. 与之对应的还有个+? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的.
Pick
从 T 中取出 一系列 K 的属性
type Pick<T, K extends keyof T> = {[P in K]: T[P];};
例子:
type NewPerson = Pick<People, 'name'>;
Exclude
从 T 中移除 一系列 U 的属性
type Exclude<T, U> = T extends U ? never : T;type T = Exclude<1 | 2, 1 | 3>
与 Exclude 类似的 Extract<T, U>(取交集)
type Extract<T, U> = T extends U ? T : never;type T = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >
将 Pick 和 Exclude 结合起来实战
interface Api {name: string;age: number}interface CustomApi extends Api {name: number;}interface CustomApi1 extends Pick<Chicken, 'age'> {name: number;}interface CustomApi2 extends Pick<Api, Exclude<keyof Api, 'name'>> {name: number;}interface CustomApi3 extends Omit<Api, 'name'> {name: number;}
类似Exclude作用的 还有 NonNullable, 将 null | undefined排除
type NonNullable<T> = T extends null | undefined ? never : T;type Test = '111' | '222' | null;type NewTest = NonNullable<Test>;
Omit
未包含
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>type Foo = Omit<{name: string, age: number}, 'name'>
Record
标记对象的 key value 类型
type Record<K extends keyof any, T> = {[P in K]: T;};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>;function mapObject(): any {}const names = { foo: "hello", bar: "world", baz: "bye" };const lengths = mapObject(names, s => s.length);type newNames = typeof lengths
ReturnType
反解
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
其实这里的 infer R 就是声明一个变量来承载传入函数签名的返回值类型(反解), 简单说就是用它取到函数返回值的类型方便之后使用. 举个例子来理解infer
type PromiseType<T> = (args: any[]) => Promise<T>;type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;async function stringPromise() {return "string promise";}type extractStringPromise = UnPromisify<typeof stringPromise>;
ReturnType demo
function TestFn() {return 'test';}type Test = ReturnType<typeof TestFn>;
和上述差不多了 我们可以依葫芦画瓢 个 PromiseType
type PromiseType<T extends Promise<any>> = T extends Promise<infer R> ? R : never;type Test = PromiseType<Promise<string>>
再结合深入一点
type PromiseReturnType<T extends () => any> = ReturnType<T> extends Promise<infer R>? R: ReturnType<T>async function test() {return { a: 1, b: '2' }}type Test = PromiseReturnType<typeof test>
Parameters
获取一个函数的所有参数类型
上面的ReturnType认识了infer, 这里直接放源码和 demo 了
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;interface IPerson {name: string}interface IFunc {(person: IPerson, count: number): boolean}type P = Parameters<IFunc>const person1: P[0] = {name: '1'}
ConstructorParameters
类似于 Parameters<T>, ConstructorParameters 获取一个类的构造函数参数
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;type DateConstrParams = ConstructorParameters<typeof Date>interface DateConstructor {new (value: number | string | Date): Date;}
业务结合,自定义
1. 有的时候重写属性
interface Test {name: string;age: number;}interface Test2 extends Test{name: Test['name'] | number}
实现 将 T 中的 key 对应的 value 设为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}
当然上面更极端的做法也可以是 排除再重写
interface Test2 extends Omit<Test, 'name'> {name: Test['name'] | number}
2. 将联合类型|转成交叉类型&
type UnionToIntersection<U> = (U extends any? (k: U) => void: never) extends ((k: infer I) => void)? I: nevertype Test = UnionToIntersection<{ a: string } | { b: number }>type Weird = UnionToIntersection<string | number | boolean>
可能有人会懵,首先得理解 Distributive conditional types, 举个例子 T extends U ? X : Y 中,当 T 是 A | B 时,会拆分成 A extends U ? X : Y | B extends U ? X : Y;再结合infer同一类型变量的多个候选类型将会被推断为交叉类型,参考举个例子:
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;type T10 = Foo<{ a: string, b: string }>;type T11 = Foo<{ a: string, b: number }>;type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;
综上:
type Result = UnionToIntersection<T1 | T2>;
- 第一步:
(U extends any ? (k: U) => void : never)会把union拆分成(T1 extends any ? (k: T1) => void : never) | (T2 extends any ? (k: T2)=> void : never),即是得到(k: T1) => void | (k: T2) => void; - 第二步:
((k: T1) => void | (k: T2) => void) extends ((k: infer I) => void) ? I : never,根据上文,可以推断出 I 为T1 & T2。
3. 数组 转换 成 union
const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const;type SuitTuple = typeof ALL_SUITS;type Suit = SuitTuple[number];
ts3.4 新语法 as const创建不可变(常量)元组类型 / 数组,所以 TypeScript 可以安全地采用窄文字类型['a', 'b']而不是更宽('a' | 'b')[]或甚至string[]类型
4. 合并参数返回值类型
function injectUser() {return { user: 1 }}function injectBook() {return { book: '2' }}const injects = [injectUser, injectBook]
type Prettify<T> = [T] extends [infer U] ? { [K in keyof U]: U[K] } : nevertype InjectTypes<T extends Array<() => object>> = T extends Array<() => infer P>? Prettify<UnionToIntersection<P>>: nevertype result = InjectTypes<typeof injects>
上面看似一堆,我们可以一步步将其拆解
type test = typeof injects;type test1<T> = T extends Array<() => infer P> ? P : nevertype test2 = test1<test>type test3 = UnionToIntersection<test2>type test4 = Prettify<test3>
其实上面的过于复杂的,我们可以采用另一个方案
type User = ReturnType<typeof injectUser>type Book = ReturnType<typeof injectBook>type result = Prettify<User & Book>
最终可以改成这样
type User = ReturnType<typeof injectUser>type Book = ReturnType<typeof injectBook>type result = User | Book
5. 常用内置类型结合封装
- PartialRecord
interface Model {name: string;age: number;}interface Validator {required?: boolean;trigger?: string;}const validateRules: Record<keyof Model, Validator> = {name: {required: true, trigger: `blur`}}type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>const validateRules: PartialRecord<keyof Model, Validator> = {name: {required: true, trigger: `blur`}}
- DeepPartial
type RecursivePartial<T> = {[P in keyof T]?:T[P] extends (infer U)[] ? RecursivePartial<U>[] :T[P] extends object ? RecursivePartial<T[P]> :T[P];};
- DeepRequired
type DeepRequired<T> = {[P in keyof T]-?:T[P] extends ((infer U)[]|undefined) ? DeepRequired<U>[] :T[P] extends (object|undefined) ? DeepRequired<T[P]> :T[P]}
6. 挑选出readonly字段
type IfEquals<X, Y, A=X, B=never> =(<T>() => T extends X ? 1 : 2) extends(<T>() => T extends Y ? 1 : 2) ? A : B;type ReadonlyKeys<T> = {[P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>}[keyof T];type A = {readonly a: stringb: number}type B = ReadonlyKeys<A>
以下参考轮子哥的解释:首先应该解释一下ReadonlyKeys里面[Q in P]的意思。P他是一个字符串,不是一个字符串的集合,所以[Q in P]实际上就是P。如果你直接写{P:T[P]}的话,你得到的是一个拥有成员变量"P"的对象,而{[Q in P]:T[P]}拿到的是变量P在这里的值(也就是 “a” 或者 “b”),而且他还把有没有 readonly的这个属性给带了回来。如果把ReadonlyKeys改成这样的类型 type
type ReadonlyKeys2<T> = {[P in keyof T]-?: { [Q in P]: T[P] }};
那我们会得到为
ReadonlyKeys2<A> 为 {readonly a: {readonly a:string};b: {b:number};}
然后我们就去调用 IfEquals。在这里我需要指出,<T>()=>T extends X ?1:2的优先级是<T>()=>(T extends X ?1:2),在T是个自由变量的情况下,我们比较的是X 和 Y究竟是不是同一个类型。比较两个泛型类型,又没有办法拿到确切的值来计算,只能直接比较一下表达式是否相同。
{readonly a : (<T>()=>T extends {readonly a:string} ? 1 : 2) extends (<T>()=>T extends {a:string} ? 1 : 2) ? never : 'a';b : (<T>()=>T extends {b:number} ? 1 : 2) extends (<T>()=>T extends {b:number} ? 1 : 2) ? never : 'b';}['a' | 'b']
ps: 这里提一下 -readonly其实就是将 T 的所有属性的 readonly 移除
然后我们来计算一下显然,以下两个表达式是不一样的<T>()=>T extends {readonly a:string} ? 1 : 2和<T>()=>T extends {a:string} ? 1 : 2而以下两个表达式是一样的<T>()=>T extends {b:number} ? 1 : 2和<T>()=>T extends {b:number} ? 1 : 2一样会得到never,不一样就得到一个字符串,这里原理是Conditional Types在有变量没被 resolve 时,extends部分必须完全一致才算相等 于是上面的类型就被简化为
{readonly a:'a';b:never;}['a' | 'b']
显然 never 就代表没有,因此你得到了’a’。 如果您阅读之后有所帮助,欢迎点赞,如果有更好的意见和批评,欢迎指出!
https://juejin.cn/post/6895538129227546638#heading-0
