TS高级特性API

Posted Songlcy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TS高级特性API相关的知识,希望对你有一定的参考价值。

  • 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的主要内容,如果未能解决你的问题,请参考以下文章

TS高级特性API

20.Flink高级特性--新特性--双流Joinjoin的分类API代码演示-WindowJoin代码演示-IntervalJoin

20.Flink高级特性--新特性--双流Joinjoin的分类API代码演示-WindowJoin代码演示-IntervalJoin

MyBatis高级特性

TS Stream 详解

具有多个片段的活动的 MVP