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 创建Dom元素
May 07 Javascript
JS获取地址栏参数的小例子
Aug 23 Javascript
AngularJS中的模块详解
Jan 29 Javascript
理解JS事件循环
Jan 07 Javascript
jQuery height()、innerHeight()、outerHeight()函数的区别详解
May 23 Javascript
详解Javascript中的原型OOP
Oct 12 Javascript
Reactjs实现通用分页组件的实例代码
Jan 19 Javascript
详解vue-cli中配置sass
Jun 21 Javascript
jQuery实现简单的回到顶部totop功能示例
Oct 16 jQuery
layer弹出层父子页面事件相互调用方法
Aug 17 Javascript
vue 使用插槽分发内容操作示例【单个插槽、具名插槽、作用域插槽】
Mar 06 Javascript
详解vue-router的导航钩子(导航守卫)
Nov 02 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 破解防盗链图片函数
2008/12/09 PHP
PHP 解决session死锁的方法
2013/06/20 PHP
php cookie使用方法学习笔记分享
2013/11/07 PHP
php实现发送微信模板消息的方法
2015/03/07 PHP
golang实现php里的serialize()和unserialize()序列和反序列方法详解
2018/10/30 PHP
Jquery.TreeView结合ASP.Net和数据库生成菜单导航条
2010/08/27 Javascript
jquery与prototype框架的详细对比
2013/11/21 Javascript
IE下双击checkbox反应延迟问题的解决方法
2014/03/27 Javascript
禁止iframe脚本弹出的窗口覆盖了父窗口的方法
2014/09/06 Javascript
js仿百度登录页实现拖动窗口效果
2016/03/11 Javascript
JS获取checkbox的个数简单实例
2016/08/19 Javascript
JS基于正则截取替换特定字符之间字符串操作示例
2017/02/03 Javascript
Nodejs高扩展性的模板引擎 functmpl简介
2017/02/13 NodeJs
微信小程序 本地数据存储实例详解
2017/04/13 Javascript
jQuery实现模糊搜索功能的方法分析
2018/06/29 jQuery
浅谈在vue中使用mint-ui swipe遇到的问题
2018/09/27 Javascript
微信小程序实现上传word、txt、Excel、PPT等文件功能
2019/05/23 Javascript
JavaScript页面加载事件实例讲解
2019/09/01 Javascript
vue 获取及修改store.js里的公共变量实例
2019/11/06 Javascript
详解vue路由
2020/08/05 Javascript
NodeJS模块Buffer原理及使用方法解析
2020/11/11 NodeJs
介绍Python的Django框架中的静态资源管理器django-pipeline
2015/04/25 Python
python中 chr unichr ord函数的实例详解
2017/08/06 Python
在Python中实现shuffle给列表洗牌
2018/11/08 Python
详解Python3 基本数据类型
2019/04/19 Python
华为校园招聘上机笔试题 扑克牌大小(python)
2020/04/22 Python
html5简介及新增功能介绍
2020/05/18 HTML / CSS
印度最大的网上花店:Ferns N Petals(鲜花、礼品和蛋糕)
2017/10/16 全球购物
我爱祖国演讲稿
2014/09/02 职场文书
党员国庆节演讲稿范文2014
2014/09/21 职场文书
学习十八大宣传标语
2014/10/09 职场文书
党的群众路线教育实践活动组织生活会发言材料
2014/10/17 职场文书
入党培养人考察意见
2015/06/08 职场文书
大学生受助感言
2015/08/01 职场文书
高二语文教学反思
2016/02/16 职场文书
《春酒》教学反思
2016/02/22 职场文书