引言
在上一篇我重点讲述了 ts 的 keyof、in 以及 infer。本期将结合一道笔试题重点讲述 ts 的一些其他内置操作符 。
本期涉及的操作符如下:
- Partial
- Required
- Readonly
- Pick<T,K extends keyof T>
- Record<K extends keyof any, T>
- Exclude<T,U>
- Extract<T,U>
- Omit<T, K extends keyof any>
首先还是先讲述一下ts中的这些高级操作符,如果都已经掌握了,可以直接跳到末尾的手撕笔试题。手撕笔试题
Partial
Partial 将属性变为可选属性。举个栗子,iUser 这个接口 name 和 age 是必须的,但是同时又有另一个接口 iOptionUser,接口属性完全一样,只是里面的 name 和 age 是可选的。比较笨的方法当然是手动再写一个。
interface iUser {
name: string;
age: number;
}
interface iOptionUser {
name?: string;
age?: number;
}
复制代码
其实,我们可以看到的是,iOptionUser 只是在属性后添加一个?接口。我们可以简单实现如下(该方法已内置)
type Partial<T> = {
[P in keyof T]?: T[P];
};
复制代码
Required
Required和Partial方法正好相反,是将属性变成必须。方法同样非常简单,可以这样实现(该方法已内置)
type Required<T> = {
[P in keyof T]-?: T[P];
};
复制代码
效果如下:
Readonly
Readonly是将属性变成只读。方法同样非常简单,可以这样实现(该方法已内置)
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
复制代码
效果如下:
Pick<T,K extends keyof T>
Pick顾名思义,就是把一些属性挑选出来。效果如下:
大家可以思考一下怎么实现,官方源码如下:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
复制代码
Record<K extends keyof any, T>
Record用于创建一个具有同类型属性值的对象。
type Record<K extends keyof any, T> = {
[P in K]: T;
};
复制代码
Exclude<T,U>
从类型 T 中剔除所有可以赋值给 U 的属性,然后构造一个类型。主要用于联合类型。
官方源码如下:
type Exclude<T, U> = T extends U ? never : T;
复制代码
Extract<T,U>
功能与 Exclude相反
type Extract<T, U> = T extends U ? T : never;
复制代码
Omit<T, K extends keyof any>
主要用于剔除interface中的部分属性。 比如接口iUser包含name、age、firstName、lastName、location属性,而接口iUser2不包含location属性,我们可以使用前面提到的Pick实现,但这样会比较复杂,所以有了Omit 操作符。
interface iUser {
name: string;
age: number;
firstName: string;
lastName: string;
location: string;
}
interface iUser2 {
name: string;
age: number;
firstName: string;
lastName: string;
}
复制代码
效果如下:
Omit源码如下:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
复制代码
手撕笔试题
这是一道 leetcode 的 ts笔试题,原题目略长,就不直接贴出来了,这里简化一下:
// 假设有一个这样的类型:
interface initInterface {
count: number;
message: string;
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>;
syncMethod<T, U>(action: Action<T>): Action<U>;
}
// 在经过 Connect 函数之后,返回值类型为
type Result {
asyncMethod<T, U>(input: T): Action<U>;
syncMethod<T, U>(action: T): Action<U>;
}
// 其中 Action<T> 的定义为:
interface Action<T> {
payload?: T
type: string
}
// 现在要求写出Connect的函数类型定义。
复制代码
首先我们需要明白这个题义,这里是需要我们把initInterface里的非函数属性去除,并且函数签名发生了变化。
- 第一步:获取函数属性
type RemoveNonFunctionProps<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProps = RemoveNonFunctionProps<initInterface>;
复制代码
2. 将只包含函数属性的类型Pick出来
type PickFunction<T> = Pick<T, RemoveNonFunctionProps<T>>;
type iFunctionInterface = PickFunction<initInterface>;
复制代码
3.接下来就是函数转换的过程,这里需要用到我
上篇博文提到的infer。
我们对比一下,转换前后的函数签名,发现只是去除了参数和返回结果的Promsie。
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
type transformAsyncMethod<T,U> = (input: T) => Action<U>;
复制代码
我们使用infer可以这样做
type TransformASyncMethod<T> = T extends (
input: Promise<infer U>
) => Promise<Action<infer S>>
? (input: U) => Action<S>
: never;
复制代码
同理我们看一下方法二,转换前后:
type syncMethod<T, U> = (action: Action<T>) => Action<U>;
type transformSyncMethod<T, U> = (action: T) => Action<U>;
复制代码
我们依旧使用infer
type TransformSyncMethod<T> = T extends (
action: Action<infer U>
) => Action<infer S>
? (action: U) => Action<S>
: never;
复制代码
所以转换函数可以这样写:
type TransformMethod<T> = T extends (
input: Promise<infer U>
) => Promise<Action<infer S>>
? (input: U) => Action<S>
: T extends (action: Action<infer U>) => Action<infer S>
? (action: U) => Action<S>
: never;
复制代码
4.整合 前三步,我们已经有了完整的思路,现在就是把Connect类型定义整合起来。
type RemoveNonFunctionProps<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type PickFunction<T> = Pick<T, RemoveNonFunctionProps<T>>;
type TransformMethod<T> = T extends (
input: Promise<infer U>
) => Promise<Action<infer S>>
? (input: U) => Action<S>
: T extends (action: Action<infer U>) => Action<infer S>
? (action: U) => Action<S>
: never;
type ConnectAll<T> = {
[K in keyof T]: TransformMethod<T[K]>;
};
type Connect<T> = ConnectAll<PickFunction<T>>;
复制代码
本次就到这里了,下面是我个人的微信公众号。
@Author: WaterMan
来源:oschina
链接:https://my.oschina.net/u/4267539/blog/4263856