TypeScript 内置高级类型编程示例


Posted in Javascript onSeptember 23, 2022

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,返回一个新的对象类型。

TypeScript 内置高级类型编程示例

这里我们通过 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),然后拼装成联合类型。

TypeScript 内置高级类型编程示例

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。

TypeScript 内置高级类型编程示例

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

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

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

ReturnType<Type>

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

TypeScript 内置高级类型编程示例

实现为:

/**
 * 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 内置类型的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JavaScript 函数惰性载入的实现及其优点介绍
Aug 12 Javascript
jQuery oLoader实现的加载图片和页面效果
Mar 14 Javascript
javascript学习笔记整理(概述、变量、数据类型简介)
Oct 25 Javascript
JS禁用页面上所有控件的实现方法(附demo源码下载)
Dec 17 Javascript
Angularjs的启动过程分析
Jul 18 Javascript
Vue ElementUI之Form表单验证遇到的问题
Aug 21 Javascript
利用10行js代码实现上下滚动公告效果
Dec 08 Javascript
Node.js成为Web应用开发最佳选择的原因
Feb 05 Javascript
vue.js删除列表中的一行
Jun 30 Javascript
vue项目首屏加载时间优化实战
Apr 23 Javascript
微信小程序实现点击效果
Jun 21 Javascript
WebRTC记录音视频流(web技术分享)
Feb 24 Javascript
详解Anyscript开发指南绕过typescript类型检查
Sep 23 #Javascript
js基于div丝滑实现贝塞尔曲线
Sep 23 #Javascript
TS 类型兼容教程示例详解
Sep 23 #Javascript
TS 类型收窄教程示例详解
Sep 23 #Javascript
JavaScript实现简单的音乐播放器
Aug 14 #Javascript
vue实现简易音乐播放器
Aug 14 #Vue.js
Vue3实现简易音乐播放器组件
Aug 14 #Vue.js
You might like
PHP字符串 ==比较运算符的副作用
2009/10/21 PHP
yii框架中的Url生产问题小结
2012/01/16 PHP
php上传文件中文文件名乱码的解决方法
2013/11/01 PHP
thinkphp视图模型查询提示ERR: 1146:Table 'db.pr_order_view' doesn't exist的解决方法
2014/10/30 PHP
ThinkPHP中公共函数路径和配置项路径的映射分析
2014/11/22 PHP
PHP实现的线索二叉树及二叉树遍历方法详解
2016/04/25 PHP
PHP实现分布式memcache设置web集群session同步的方法
2018/04/10 PHP
laravel按天、按小时,查询数据的实例
2019/10/09 PHP
javascript 特性检测并非浏览器检测
2010/01/15 Javascript
js制作的鼠标悬浮时产生的下拉框效果
2012/10/27 Javascript
jquery.post用法之type设置问题
2014/02/24 Javascript
jQuery学习笔记之总体架构
2014/06/03 Javascript
用jquery模仿的a的title属性的例子
2014/10/22 Javascript
jQuery选择器之基本选择器与层次选择器
2015/03/03 Javascript
js简单实现调整网页字体大小的方法
2016/07/23 Javascript
AngularJS中$http服务常用的应用及参数
2016/08/22 Javascript
关于vue组件事件属性穿透详解
2019/10/28 Javascript
ES2020系列之空值合并运算符 '??'
2020/07/22 Javascript
Javascript执行上下文顺序的深入讲解
2020/11/04 Javascript
微信小程序实现登录注册功能
2020/12/29 Javascript
OpenCV实现人脸识别
2017/04/07 Python
python flask中静态文件的管理方法
2018/03/20 Python
详解Python安装scrapy的正确姿势
2018/06/26 Python
PyQt弹出式对话框的常用方法及标准按钮类型
2019/02/27 Python
基于TensorFlow常量、序列以及随机值生成实例
2020/01/04 Python
python对execl 处理操作代码
2020/06/22 Python
医科学校毕业生自荐信
2013/11/09 职场文书
最新自我评价范文
2013/11/16 职场文书
运动会方阵解说词
2014/02/12 职场文书
销售求职信范文
2014/05/26 职场文书
学校纪律作风整改措施思想汇报
2014/10/11 职场文书
美术教师求职信范文
2015/03/20 职场文书
2016年中秋节慰问信
2015/12/01 职场文书
2016年小学教师政治学习心得体会
2016/01/23 职场文书
MySQL Threads_running飙升与慢查询的相关问题解决
2021/05/08 MySQL
Win Server2016远程桌面如何允许多用户同时登录
2022/06/10 Servers