ts高级特性api(代码片段)

Songlcy Songlcy     2022-12-29     192

关键词:

  • 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>

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里的非函数属性去除,并且函数签名发生了变化。

  1. 第一步:获取函数属性
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>>;
  • keyof
  • in
  • infer 关键字
  • Parameters
  • ReturnType
  • InstanceType
  • ConstructorParameters
  • ThisParameterType
  • OmitThisParameter

 

通过上述操作符的学习,希望能达到以下效果:

  • 不再为大佬写的 ts 定义而苦恼了
  • 看源码定义不再吃力了
  • 自己的 ts 代码更加智能,不再是满屏的 any 了。

下面我将结合具体实栗向大家讲述 ts 中的高级操作符。

keyof

定义

keyof与Object.keys略有相似,只是 keyof 是取 interface 的键,而且 keyof 取到键后会保存为联合类型。

interface iUserInfo 
  name: string;
  age: number;

type keys = keyof iUserInfo;
复制代码

 

keyof 的简单栗子

我们有这样一个需求,实现一个函数 getValue 取得对象的 value。在未接触 keyof 时,我们一般会这样写:

function getValue(o: object, key: string) 
  return o[key];

const obj1 =  name: '张三', age: 18 ;
const name = getValue(obj1, 'name');
复制代码

但是,这样写就丧失了 ts 的优势:

  • 无法确定返回值类型
  • 无法对 key 进行约束,可能会犯拼写的错误

这时我们可以使用 keyof 来增强 getValue 函数的类型功能。

 

使用 keyof 后我们可以看到,可以完整的提示可以输入的值,当拼写错误时也会有清晰的提示。

 

function getValue<T extends Object, K extends keyof T>(o: T, key: K): T[K] 
  return o[key];


const obj1 =  name: '张三', age: 18 ;
const a = getValue(obj1, 'hh');
复制代码

in

in用于取联合类型的值。主要用于数组和对象的构造。

type name = 'firstName' | 'lastName';
type TName = 
  [key in name]: string;
;
复制代码

 

const data1 = [
  
    a1: 'a',
    b1: 'b',
    c1: 'c',
    d1: 'd',
  ,
];

const data2 = [
  
    a2: 'a',
    b2: 'b',
  ,
];
复制代码

但切记不要用于 interface,否则会出错

 

infer

先看官方解释:

Within the extends clause of a conditional type, it is now possible to have infer declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have multiple infer locations for the same type variable.

翻译过来就是:

现在在有条件类型的 extends 子语句中,允许出现 infer 声明,它会引入一个待推断的类型变量。 这个推断的类型变量可以在有条件类型的 true 分支中被引用。 允许出现多个同类型变量的 infer。

初步看来,这个 ts 关键字限制比较多,也是笔者觉得比较难理解的,但是它对我们获取一些比较复杂的类型特别有用。使用过程中需要注意以下几个关键点

  • 只能出现在有条件类型的 extends 子语句中;
  • 出现 infer 声明,会引入一个待推断的类型变量;
  • 推断的类型变量可以在有条件类型的 true 分支中被引用;
  • 允许出现多个同类型变量的 infer

要彻底理解这个关键词的使用必须结合一些实例。

infer 实例

使用 infer 获取函数参数 Parameters

比如我们这里定义了一个函数类型 TArea,现在要实现将函数的参数类型取出来,我们该怎么做呢?

type TArea = (width: number, height: number) => number;
type params = Parameters<TArea>;
复制代码

 

其实 Parameters 方法 ts 已内置,源码如下:

type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;
复制代码

我们仔细研读一下以上源码,发现遵循我们上面所说的 infer 满足的四个特点:

  • 只能出现在有条件类型的 extends 子语句中;
  • 出现 infer 声明,会引入一个待推断的类型变量;
  • 推断的类型变量可以在有条件类型的 true 分支中被引用;
  • 允许出现多个同类型变量的 infer

这里再啰嗦几句,因为我们要获取函数参数,所以传递的参数必须是个函数,所以有 T extends (...args: any) => any,由于我们要获取的是函数参数的类型,所以 infer 出现在了函数参数位置。

同理获取函数返回值的方法就呼之欲出了,如果还是写不出来,当我没说。

使用 infer 获取函数返回值 ReturnType

ReturnType 方法 ts 已内置

type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;
复制代码

再看一下图,不要说我骗你!

 

 

了不得了,infer 真是太强大了,下面我们继续看 infer 如何获取一个类实例的类型。

获取实例类型 InstanceType

type InstanceType<T extends new (...args: any) => any> = T extends new (
  ...args: any
) => infer R
  ? R
  : any;
复制代码

偷偷告诉你,聪明的 ts 官方也内置了这个工具。

 

获取构造函数类型 ConstructorParameters

该方法 ts 已内置我们看一下源码

type ConstructorParameters<
  T extends new (...args: any) => any
> = T extends new (...args: infer P) => any ? P : never;
复制代码

我们可以这样使用它

 

获取参数 this 参数 ThisParameterType

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any
  ? U
  : unknown;
复制代码

 

剔除 this 参数 OmitThisParameter

实现效果如下,大家可以自己手动实现一下,这可以很好的训练一下 infer 的使用。

 

官方源码如下:

type OmitThisParameter<T> = unknown extends ThisParameterType<T>
  ? T
  : T extends (...args: infer A) => infer R
  ? (...args: A) => R
  : T;
复制代码

我们可以这样理解:如果传递的函数不包含 this 参数,则直接返回。以下语法用于判断是否包含 this 参数

unknown extends ThisParameterType<T>
复制代码

总结

我们重点讲述了 ts 中 keyof 和 infer 的高级用法,下面以两个思考题结束本篇文章,具体答案会在下篇文章揭晓。

思考题 1

这是一道 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的函数类型定义。
复制代码

思考题二

// 有原数组如下
const data1 = [
  
    a1: 'a',
    b1: 'b',
    c1: 'c'
  
];
// 实现一个函数 transformData ,传递一个keyMap后,结果返回经过keyMap转换后的数组

const A2 = transformData(data1,  a1: 'a2' ); // 返回 [a2: 'a']
const A2 = transformData(data1,  a1: 'a2',b2: 'b1' ); // 返回 [a2: 'a', b2: 'b']

// 要求用ts完成,必须有完善类型推断,不能出现any


作者:WaterMan
链接:https://juejin.cn/post/6844904145732763655
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

ts高级特性api(代码片段)

PartialRequiredReadonlyPick<T,KextendskeyofT>Record<Kextendskeyofany,T>Exclude<T,U>Extract<T,U>Omit<T,Kextendskeyofany>PartialPartial将属性变为可选属性。举个栗子,iUser这个接口name和a 查看详情

20.flink高级特性--新特性--双流joinjoin的分类api代码演示-windowjoin代码演示-intervaljoin(代码片段)

20.Flink高级特性–新特性–双流Join20.1.join的分类20.2.API20.3.代码演示-WindowJoin20.4.代码演示-IntervalJoin20.Flink高级特性–新特性–双流Join20.1.join的分类双流Join是Flink面试的高频问题。一般情况下说明以下几点就可以hold了:Join大... 查看详情

23.flink-高级特性-新特性-streamingfliesink介绍代码演示flink-高级特性-新特性-flinksql整合hive添加依赖和jar包和配置(代码片段)

23.Flink-高级特性-新特性-StreamingFlieSink23.1.介绍23.2.代码演示24.Flink-高级特性-新特性-FlinkSQL整合Hive24.1.介绍24.2.版本24.3.添加依赖和jar包和配置24.4.FlinkSQL整合Hive-CLI命令行整合24.5.FlinkSQL整合Hive-代码整合23.Flink-高级特性-新特性-Stream... 查看详情

23.flink-高级特性-新特性-streamingfliesink介绍代码演示flink-高级特性-新特性-flinksql整合hive添加依赖和jar包和配置(代码片段)

23.Flink-高级特性-新特性-StreamingFlieSink23.1.介绍23.2.代码演示24.Flink-高级特性-新特性-FlinkSQL整合Hive24.1.介绍24.2.版本24.3.添加依赖和jar包和配置24.4.FlinkSQL整合Hive-CLI命令行整合24.5.FlinkSQL整合Hive-代码整合23.Flink-高级特性-新特性-Stream... 查看详情

typescriptauth.api.ts(代码片段)

查看详情

23.flink-高级特性-新特性-streamingfliesink介绍代码演示flink-高级特性-新特性-flinksql整合hive添加依赖和jar包和配置(代码片段)

23.Flink-高级特性-新特性-StreamingFlieSink23.1.介绍23.2.代码演示24.Flink-高级特性-新特性-FlinkSQL整合Hive24.1.介绍24.2.版本24.3.添加依赖和jar包和配置24.4.FlinkSQL整合Hive-CLI命令行整合24.5.FlinkSQL整合Hive-代码整合23.Flink-高级特性-新特性-Stream... 查看详情

dubbo3高级特性「提升系统安全性」ssl的安全服务能力(代码片段)

Dubbo3的TLS保证传输安全特性说明内置的DubboNettyServer和新引入的gRPC协议都提供了基于TLS的安全链路传输机制。TLS的配置都有统一的入口。使用场景对全链路有加密需求的用户可以使用TLS。使用方式API的模式使用方式Provider端建立... 查看详情

#22.flink-高级特性-新特性-异步io原理(代码片段)

22.Flink-高级特性-新特性-异步IO-了解22.1.原理22.1.1.异步IO操作的需求https://nightlies.apache.org/flink/flink-docs-release-1.12/dev/stream/operators/asyncio.htmlAsyncI/O是阿里巴巴贡献给社区的一个呼声非常高的特性,于1.12版本引入。主要目的是为... 查看详情

高级oop特性(代码片段)

PHP不支持的高级OPP特性 PHP不支持通过函数重载实现多态PHP不支持多重继承PHP不支持根据所修改数据类型为操作符赋予新的含义对象克隆克隆实例 在对象前面添加clone关键字来克隆对象,对象的属性值都被继承,克隆的对... 查看详情

python高级特性(代码片段)

切片(tuple)有了切片操作,很多地方循环就不再需要了。Python的切片非常灵活,一行代码就可以实现很多行循环才能完成的操作。操作对象:list、tuple、str;替代了其他语言的截取函数,如substring()应用场景:对经常取指定索... 查看详情

cocos技术派|ts版属性面板定义高级篇(代码片段)

1音频剪辑音频剪辑是一个比较特殊的节点,它是一个资源,本质上其实是一个音频的url,但是我们不能直接使用string来定义。@property(    type: cc.AudioClip,    displayName:"背景音乐")bgm: cc.AudioClip = null;2... 查看详情

python高级特性(代码片段)

...、迭代器此文章参考廖雪峰大神的官网,地址:高级特性-廖雪峰的官方网站(liaoxuefeng.com)一、切片在python的使用中,对于列表、元组的元素取值是非常常见的,例如:注意:切片是顾头不顾尾的>> 查看详情

rabbitmq--高级特性(代码片段)

在上一篇文章讲解MQ消息可靠性投递和幂等性中有提到confirm机制的重要性,现在更相信的说明一下一、Confirm机制  Confirm就是消息确认,当Producer发送消息,如果Broker收到消息,会回复一个应答,我们可以以此来确认消息是否... 查看详情

021css高级特性(代码片段)

一:元素的显示与影藏1.比较常见的单词  dispaly,visibility,overflow  2.display案例  如果影藏了,这个元素就看不见了,然后也不保留位置1<!DOCTYPEhtml>2<htmllang="en">3<head>4<metacharset="UTF-8">5<title>Document</... 查看详情

jvm高级特性与实践(十三):线程实现与java线程调度(代码片段)

JVM高级特性与实践(一):Java内存区域与内存溢出异常JVM高级特性与实践(二):对象存活判定算法(引用)与回收JVM高级特性与实践(三):垃圾收集算法与垃圾收集器实现JVM高级特... 查看详情

python的高级特性(代码片段)

...、迭代器此文章参考廖雪峰大神的官网,地址:高级特性-廖雪峰的官方网站(liaoxuefeng.com)一、切片在python的使用中,对于列表、元组的元素取值是非常常见的,例如:注意:切片是顾头不顾尾的>> 查看详情

消息队列rabbitmq高级特性(代码片段)

一.消息的可靠投递在使用RabbitMq的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败的场景。RabbitMQ为我们提供了两种方式用来控制消息的投递可靠性rabbitMQ整个消息投递过程为: producer-> rabbitMQbroker->exchange-... 查看详情

springboot高级特性-缓存(代码片段)

缓存:将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能springboot中使用缓存可以缓存方法的返回值等等避免多次查询数据库springboot的缓存有以下层级关系CachingProvider缓存提供者—>管理和控制... 查看详情