进来看看,TypeScript居然还能这么玩
作者: 徐海强
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>
// error: Property 'name1' is missing in type ...
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'>; // { name: string; }
Exclude
从 T 中移除 一系列 U 的属性
type Exclude<T, U> = T extends U ? never : T;
// demo
type T = Exclude<1 | 2, 1 | 3> // => 2
与 Exclude 类似的 Extract<T, U>
(取交集)
type Extract<T, U> = T extends U ? T : never;
type T = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' > // => // 'b'|'c'
将Pick 和 Exclude 结合起来实战
// 比如后端接口定义好的返回类型是这个,但是我们并不能直接修改
interface Api {
name: string;
age: number
}
// error: Types of property 'name' are incompatible.
interface CustomApi extends Api {
name: number;
}
// change
interface CustomApi1 extends Pick<Chicken, 'age'> {
name: number;
}
// 但是上面还是太复杂了,你需要把所有属性挑拣起来,结合 Exclude 将key全拿出来 可以省事很多
interface CustomApi2 extends Pick<Api, Exclude<keyof Api, 'name'>> {
name: number;
}
// 上述其实 就是Omit的源码
interface CustomApi3 extends Omit<Api, 'name'> {
name: number;
}
类似Exclude
作用的 还有 NonNullable
,将 null | undefined
排除
type NonNullable<T> = T extends null | undefined ? never : T;
// demo
type Test = '111' | '222' | null;
type NewTest = NonNullable<Test>; // '111' | '222'
Omit
未包含
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
// demo
type Foo = Omit<{name: string, age: number}, 'name'> // -> { age: number }
Record
标记对象的 key value类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// demo
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>;
// 这里简易实现,否则报ts(2391)错误
function mapObject(): any {}
const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length);
type newNames = typeof lengths // => { foo: number, bar: number, baz: number }
ReturnType
反解
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
其实这里的 infer R
就是声明一个变量来承载传入函数签名的返回值类型(反解), 简单说就是用它取到函数返回值的类型方便之后使用.
举个例子来理解infer
// 反解Promise类型
type PromiseType<T> = (args: any[]) => Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
// demo
async function stringPromise() {
return "string promise";
}
type extractStringPromise = UnPromisify<typeof stringPromise>; // string
ReturnType demo
// demo
function TestFn() {
return 'test';
}
type Test = ReturnType<typeof TestFn>; // => string
和上述差不多了 我们可以依葫芦画瓢 个 PromiseType
type PromiseType<T extends Promise<any>> = T extends Promise<infer R> ? R : never;
// demo
type Test = PromiseType<Promise<string>> // => 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> // Test 的类型为 { a: number; b: string }
Parameters
获取一个函数的所有参数类型
上面的ReturnType
认识了infer
,这里直接放源码和demo了
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
// demo
interface IPerson {name: string}
interface IFunc {
(person: IPerson, count: number): boolean
}
type P = Parameters<IFunc> // => [IPerson, number]
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;
// demo
type DateConstrParams = ConstructorParameters<typeof Date> // => string | number | Date
// 这里补充一下,源码中Date构造器定义
interface DateConstructor {
new (value: number | string | Date): Date;
}
业务结合,自定义
1. 有的时候重写属性
interface Test {
name: string;
age: number;
}
// error: Type 'string | number' is not assignable to type 'string'
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
: never
type Test = UnionToIntersection<{ a: string } | { b: number }> // => { a: string } & { b: number }
// 但是 我们可以例外推断个例子
type Weird = UnionToIntersection<string | number | boolean> // => never
// 因为不可能有一个字符串和数字以及真与假的值。
可能有人会懵,首先得理解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 }>; // string
type T11 = Foo<{ a: string, b: number }>; // string | 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 }>; // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
综上:
type Result = UnionToIntersection<T1 | T2>; // => 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; // TS 3.4
type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs']
type Suit = SuitTuple[number]; // union type : 'hearts' | 'diamonds' | 'spades' | 'clubs'
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]
// 利用第2个点的拓展 UnionToIntersection
// 将联合类型汇总
type Prettify<T> = [T] extends [infer U] ? { [K in keyof U]: U[K] } : never
type InjectTypes<T extends Array<() => object>> = T extends Array<() => infer P>
? Prettify<UnionToIntersection<P>>
: never
type result = InjectTypes<typeof injects> // Test 的类型为 { user: number, book: string }
上面看似一堆,我们可以一步步将其拆解
type test = typeof injects; // => ((() => { user: number;}) | (() => { book: string;}))[]
type test1<T> = T extends Array<() => infer P> ? P : never
type test2 = test1<test> // =>{user: number;} | { book: string;}
// 这一步就可以用UnionToIntersection 转为联合类型
type test3 = UnionToIntersection<test2>
type test4 = Prettify<test3> // =>{ user: number, book: string }
其实上面的过于复杂的,我们可以采用另一个方案
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`}
// error: Property age is missing in type...
}
// 解决
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: string
b: number
}
type B = ReadonlyKeys<A> // => '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']
// js的对象取值联合类型
显然never就代表没有,因此你得到了'a'。 如果您阅读之后有所帮助,欢迎点赞,如果有更好的意见和批评,欢迎指出!
转载自:https://juejin.cn/post/6895538129227546638