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 相关文章推荐
广告显示判断
Aug 31 Javascript
ext监听事件方法[初级篇]
Apr 27 Javascript
JS实现在Repeater控件中创建可隐藏区域的代码
Sep 16 Javascript
动态加载JS文件的三种方法
Nov 08 Javascript
禁止ajax缓存获取程序最新数据的方法
Nov 19 Javascript
JQuery each()嵌套使用小结
Apr 18 Javascript
JS实现一个列表中包含上移下移删除等功能
Sep 24 Javascript
jquery实现通用的内容渐显Tab选项卡效果
Sep 07 Javascript
前端程序员必须知道的高性能Javascript知识
Aug 24 Javascript
js方法数据验证的简单实例
Sep 17 Javascript
vue-cli与webpack处理静态资源的方法及webpack打包的坑
May 15 Javascript
详解vuex commit保存数据技巧
Dec 25 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开源建站平台小结
2010/04/22 PHP
php去除换行符的方法小结(PHP_EOL变量的使用)
2013/02/16 PHP
基于PHP代码实现中奖概率算法可用于刮刮卡、大转盘等抽奖算法
2015/12/20 PHP
PHP将整数数字转换为罗马数字实例分享
2019/03/17 PHP
ThinkPHP框架结合Ajax实现用户名校验功能示例
2019/07/03 PHP
来自国外的页面JavaScript文件优化
2010/12/08 Javascript
Jquery Ajax请求代码(2)
2011/01/07 Javascript
基于JQuery实现相同内容合并单元格的代码
2011/01/12 Javascript
JSON.parse()和JSON.stringify()使用介绍
2014/06/20 Javascript
js判断某个字符出现的次数的简单实例
2016/06/03 Javascript
JavaScript如何实现图片懒加载(lazyload) 提高用户体验(增强版)
2016/11/30 Javascript
详解angularjs中如何实现控制器和指令之间交互
2017/05/31 Javascript
jQuery Form插件使用详解_动力节点Java学院整理
2017/07/17 jQuery
Vue 组件注册实例详解
2019/02/23 Javascript
使用nodejs分离html文件里的js和css详解
2019/04/12 NodeJs
微信小程序websocket实现即时聊天功能
2019/05/21 Javascript
解决layer.msg 不居中 ifram中的问题
2019/09/05 Javascript
浅谈element中InfiniteScroll按需引入的一点注意事项
2020/06/05 Javascript
vue如何搭建多页面多系统应用
2020/06/17 Javascript
[01:48]DOTA2 2015国际邀请赛中国区预选赛第二日战报
2015/05/27 DOTA
Python 字符串转换为整形和浮点类型的方法
2018/07/17 Python
python实现公司年会抽奖程序
2019/01/22 Python
Python初学者常见错误详解
2019/07/02 Python
python文字转语音的实例代码分析
2019/11/12 Python
新手学习Python2和Python3中print不同的用法
2020/06/09 Python
关于tf.matmul() 和tf.multiply() 的区别说明
2020/06/18 Python
python爬虫爬取网页数据并解析数据
2020/09/18 Python
机械系大学毕业生推荐信
2013/11/27 职场文书
敬老院院长事迹材料
2014/05/21 职场文书
安全生产知识竞赛活动总结
2014/07/07 职场文书
医院反腐倡廉演讲稿
2014/09/16 职场文书
财会专业大学生求职信
2014/09/26 职场文书
个人事迹材料怎么写
2014/12/30 职场文书
入党个人总结范文
2015/03/02 职场文书
2015暑期社会实践通讯稿
2015/07/18 职场文书
JavaScript中document.activeELement焦点元素介绍
2021/11/27 Javascript