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 相关文章推荐
基于jquery的返回顶部效果(兼容IE6)
Jan 17 Javascript
jQuery div层的放大与缩小简单实现代码
Mar 28 Javascript
解析jQuery与其它js(Prototype)库兼容共存
Jul 04 Javascript
Bootstrap选项卡与Masonry插件的完美结合
Jul 06 Javascript
jQuery如何解决IE输入框不能输入的问题
Oct 08 Javascript
Bootstrap警告(Alerts)的实现方法
Mar 22 Javascript
基于原生js运动方式关键点的总结(推荐)
Oct 01 Javascript
基于模板引擎Jade的应用(详解)
Dec 12 Javascript
jQuery+CSS实现的table表格行列转置功能示例
Jan 08 jQuery
浅谈vuex中store的命名空间
Nov 08 Javascript
javascript设计模式 ? 观察者模式原理与用法实例分析
Apr 22 Javascript
深入讲解Vue中父子组件通信与事件触发
Mar 22 Vue.js
详解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 base64+gzinflate压缩编码和解码代码
2008/10/03 PHP
探讨PHP调用时间格式的参数详解
2013/06/06 PHP
php发送get、post请求的6种方法简明总结
2014/07/08 PHP
ThinkPHP使用PHPExcel实现Excel数据导入导出完整实例
2014/07/22 PHP
详解PHP归并排序的实现
2016/10/18 PHP
PHP实现判断数组是一维、二维或几维的方法
2017/02/06 PHP
ExtJs使用IFrame的实现代码
2010/03/24 Javascript
jquery插件制作 自增长输入框实现代码
2012/08/17 jQuery
Extjs4 关于Store的一些操作(加载/回调/添加)
2013/04/18 Javascript
Vuejs第十二篇之动态组件全面解析
2016/09/09 Javascript
实例浅析js的this
2016/12/11 Javascript
ES6通过babel转码使用webpack使用import关键字
2016/12/13 Javascript
Node.js与Sails redis组件的使用教程
2017/02/14 Javascript
利用JavaScript如何查询某个值是否数组内
2017/07/30 Javascript
node.js到底要不要加分号浅析
2018/07/11 Javascript
使用Vue父子组件通信实现todolist的功能示例代码
2019/04/11 Javascript
Python字符串的encode与decode研究心得乱码问题解决方法
2009/03/23 Python
讲解python参数和作用域的使用
2013/11/01 Python
python计算文本文件行数的方法
2015/07/06 Python
详解python eval函数的妙用
2017/11/16 Python
Python字典及字典基本操作方法详解
2018/01/30 Python
通过shell+python实现企业微信预警
2019/03/07 Python
Python基础学习之类与实例基本用法与注意事项详解
2019/06/17 Python
python如何写个俄罗斯方块
2020/11/06 Python
HTML5利用约束验证API来检查表单的输入数据的代码实例
2016/12/20 HTML / CSS
小程序canvas中文字设置居中锚点
2019/04/16 HTML / CSS
天猫国际进口超市直营:官方直采,一站购齐
2017/12/11 全球购物
Electric官网:美国高级眼镜和配件品牌
2020/06/04 全球购物
运动会领导邀请函
2014/01/10 职场文书
幼儿园消防安全制度
2014/01/26 职场文书
大学生就业求职信
2014/06/12 职场文书
诚信考试标语
2014/06/24 职场文书
预备党员2014年第四季度思想汇报范文
2014/10/25 职场文书
产品调价通知函
2015/04/20 职场文书
Python实现视频中添加音频工具详解
2021/12/06 Python
Centos7 Shell编程之正则表达式、文本处理工具详解
2022/08/05 Servers