TypeScript 内置高级类型编程示例

 更新时间:2022年09月21日 10:56:41   作者:前端西瓜哥  
这篇文章主要为大家介绍了TypeScript 内置高级类型编程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

TypeScript 类型编程

TypeScript 的类型系统,最基本的是简单对应 JavaScript 的 基本类型,比如 string、number、boolean 等,然后是新增的 tuple、enum、复合类型、交叉类型、索引类型等 增强类型

这里会有一个问题,就是函数声明支持不同类型的重复编写问题,比如我的一个函数要接收一个数组,然后从中取中一个元素。

一旦我们传入的数组类型不同,都要写多一个 type 别名,未免太繁琐。

type getStrItem = (items: string[]) => string;
type getNumItem = (items: number[]) => number;
// ... 每增加一种类型都要写多了一个 type 别名
const getStrFirst: getStrItem = (a) => {
    return a[0];
}

为解决这个问题,TypeScript 引入了 泛型,让类型也能成为参数了。

type getItem<T> = (items: T[]) => T
const getStrFirst: getItem<string> = (a) => {
    return a[0];
}

上面的 T 就是一个类型参数,当我们通过 类型别名<具体类型> 形式(上面代码对应 getItem<string>),我们就能得到一个具体的类型了。

鉴于 JavaScript 太灵活,TypeScript 实现的是结构类型系统,我们又觉得泛型的简单推到 T 的粒度还是不够细,我们希望能够获取 T 内部的结构。

于是,TypeScript 在泛型的基础上,又提供了 类型编程,通过一些语法,我们可以拿到 T 下更细粒度的类型,或通过判断拿到其他类型。

这个也被大家戏称为 类型体操。可能是因为实现起来花里胡哨像是在参加体操大赛的原因。

总结一下,从类型能力上的增强的过程来说,就是:

基本类型 -> 泛型 -> 类型编程(类型体操)

TypeScript 内置高级类型

TS 代码版本为 4.8.2

下面我们来看一下 TypeScript 内置的几个高级类型,它们用了类型编程。

Pick<Type, Keys>

Pick 的作用是,从 T 类型(对象类型)中,提取出 K(联合类型)圈定的 key,返回一个新的对象类型。

这里我们通过 Pick 提取了需要的 pos 和 radius 物理信息属性。

看看 Pick 的实现:

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

首先我们看等号左侧的 <T, K extends keyof T>,类型参数有两个,T 和 K。

先说类型参数命名

类型变量命名和写 JS 变量一样,随意起名。但建议首字母大写,以防止和一些关键字混淆(比如 extends, as, infer),这些关键词都是小写的。

T 通常代表一个要被分析的类型(Type),K 通常代表对象属性名(Key)。就像数学中函数的 x 和 y 一样,想不到好的命名就用这俩。

keyof 是类型运算符,用于提取对象的属性(key),然后拼装成联合类型。

extends 用于限制类型参数的范围。比如 <T extends string> 表示 T 类型必须是 string 的子类,像字面量的 "a" 或 string 都是 string 的子类。如果不是 string 子类,编译无法通过。

还有一种是 extends ? : 的类似 JS 中三元运算符的语法,它在等号的右侧,用于实现条件判断。它和前面提到的 extends 不是同一样东西,后面我会说到。

Ok,我们整体看看 <T, K extends keyof T> 代表什么意思。它表示传入 T 和 K 两个类型参数,然后 K 必须是 T 的属性组成的联合类型中的一部分。

我们再看看等号右边 { [P in K]: T[P]; };,它是对类型进行 重映射

in 用于对联合类型进行遍历。也就是遍历我们需要用到的 key,作为索引 P,然后它的值还是用对应的 T[P]。

Exclude<UnionType, ExcludedMembers>

Exclude 的作用是,从联合类型中剔除掉一些类型。

实现如下:

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

这里涉及到一个经常用到的 条件语法extends ? :,你可以把它类比为 JS 中的三元表达式(即 condition ? a : b)。

为了更好的讲解,我们实现一个类型 IsNumber,判断一个类型是否为数值类型。

type IsNumber<T> = T extends number ? true : false;
// 使用
type A = IsNumber<1> // true
type B = IsNumber<"str"> // false

T extends number 判断 T 是否为 number 的子类,如果是的话,返回 true,否则返回 false。

需注意和前面的类型参数上 extends 是完全不同的东西。

回到我们的 Exclude,逻辑就很清楚了,就是判断 T 是否为 U 的子类,如果是的话,返回 never(效果是被丢弃);否则返回 T。

你是不是有点奇怪结果,逻辑看起来不应该是 "a" | "b" | "c" 不是 "b" 的子类,返回 "a" | "b" | "c" 吗?怎么编程了 "a" | "c"?

其实这是联合类型的特殊逻辑,如果联合类型使用了 extends,它就会被打散,变成多个独立的类型进行判断,最后再组合起来

所以真正逻辑是, "a" | "b" | "c" 被打散,变成依次判断 "a" 、"b"、"c" 是否为 "b" 的子类,分别得到  "a" 、never、"c",然后联合起来,就变成了  "a" | "c"。

ReturnType<Type>

获取函数类型的返回值类型。

实现为:

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

等号左侧的 (...args: any) => any 代表一个任意函数类型,用于限制传入参数的类型。

然后我们看到了一个新的关键词 infer,代表引用的意思,用于类型推导。

extends 和 infer 搭配,可以实现 模式匹配,如果 extends 匹配成功,infer 就能推导获得对应的类型。

如果你了解  JS 的正则表达式,你会发现它们很像,infer 好比是捕获组。

'ABC'.replace(/A(.)C/, '$1') 
// 'B'。提取了模式上匹配的一个字符串

T extends (...args: any) => infer R ? R : any; 中,我们给返回值部分设置了 infer,并提供了一个局部变量 R。

如果 extends 条件判断是继承关系,那么变量 R 就会被赋值函数的返回值。

后面的判断为真的分支(? 后面的表达式)就能拿到这个 R。判断为假的分支就无法拿到,因为匹配失败了。

这个 extends + infer 其实就是类型体操的精髓,可以在传入类型 T 继续拆分,拿到更细粒度的类型。

更多类型体操学习

还有更多的类型编程的技巧因为篇幅原因就不说了,比如还有:

  • as 运算符可以做类型索引的重映射;
  • 通过数组的 "length" 可以实现数字运算;
  • 通过递归实现循环逻辑;
  • 一些特殊的类型(比如 never)的处理等。

TypeScript 的类型是图灵完备的,可以实现各种判断、循环、加减的逻辑。当然某些逻辑实现起来很繁琐就是了。

它的语法也是与众不同:它做了 “压缩”。一个类型的编程只是一个表达式,需要用 extend ? : 的方式不停嵌套实现逻辑。TS 类型体操学起来,某种意义上确实有点像学一门新的语言,而且有那么一点古怪。

以上就是TypeScript 内置高级类型编程示例的详细内容,更多关于TypeScript 内置类型的资料请关注脚本之家其它相关文章!

相关文章

  • Skypack布局前端基建实现过程详解

    Skypack布局前端基建实现过程详解

    这篇文章主要为大家介绍了Skypack布局前端基建过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • 微信小程序 网络API Websocket详解

    微信小程序 网络API Websocket详解

    这篇文章主要介绍了微信小程序 网络API Websocket详解的相关资料,需要的朋友可以参考下
    2016-11-11
  • ajax与302响应代码测试

    ajax与302响应代码测试

    服务器端的响应是302 Found,在ajax的回调函数中能够获取这个状态码吗?能够从Response Headers中得到Location的值进行重定向吗?让我们来一起动手写写代码看看实际情况吧。
    2013-10-10
  • 浅谈JS正则RegExp对象

    浅谈JS正则RegExp对象

    这篇文章主要介绍JS正则RegExp对象,正则表达式是描述字符模式的对象,用于对字符串模式匹配及检索替换,是对字符串执行模式匹配的强大工具。下面就来看具体详情,需要的朋友可以参考一下
    2021-10-10
  • 微信小程序 后台https域名绑定和免费的https证书申请详解

    微信小程序 后台https域名绑定和免费的https证书申请详解

    这篇文章主要介绍了微信小程序 后台https域名绑定和免费的https证书申请详解的相关资料,需要的朋友可以参考下
    2016-11-11
  • 关于JavaScript防抖与节流的区别与实现

    关于JavaScript防抖与节流的区别与实现

    这篇文章主要介绍关于JavaScript防抖与节流的区别与实现,防抖就是用户多次触发事件,在用户一直触发事件中,事件不会执行,只有在用户停止触发事件一段时间之后再执行这个事件一次,二节流是用户多次触发事件,具体详情一i起来学习下面文章内容吧
    2021-10-10
  • 微信小程序 SocketIO 实例讲解

    微信小程序 SocketIO 实例讲解

    这篇文章主要介绍了微信小程序 SocketIO 的相关资料,这里给大家提供了一个简单实例,需要的朋友可以参考下
    2016-10-10
  • JS创建对象常用设计模式工厂构造函数及原型

    JS创建对象常用设计模式工厂构造函数及原型

    本篇带来你一定熟知的、用于创建对象的三种设计模式:工厂模式、构造函数模式、原型模式,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-07-07
  • 业务层hooks封装useSessionStorage实例详解

    业务层hooks封装useSessionStorage实例详解

    这篇文章主要为大家介绍了业务层hooks封装useSessionStorage实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 微信小程序 选项卡的简单实例

    微信小程序 选项卡的简单实例

    这篇文章主要介绍了微信小程序 选项卡的简单实例的相关资料,需要的朋友可以参考下
    2017-05-05

最新评论