类型体操:探究 TypeScript 内置高级类型
xsobi 2025-01-06 16:07 1 浏览
大家好,我是前端西瓜哥,今天来做做 TS 类型体操。
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,返回一个新的对象类型。
image-20220919230929011
这里我们通过 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 类型体操学起来,某种意义上确实有点像学一门新的语言,而且有那么一点古怪。
结尾
我是前端西瓜哥,欢迎关注我,学习更多前端知识。
- 上一篇:笔记:前端自学基础之正则表达式
- 下一篇:JavaScript 正则表达式详解大全
相关推荐
- 全网最详细解决Windows下Mysql数据库安装后忘记初始root 密码方法
-
一、准备重置root的初始化密码Win+R键启动命令输入窗口;输入cmd打开命令执行窗口;##界面如下##输入命令:netstopmysqld#此操作会停止当前运行的...
- Spring Boot数据库密码加密的配置方法
-
前言由于系统安全的考虑,配置文件中不能出现明文密码的问题,本文就给大家详细介绍下springboot配置数据库密码加密的方法,下面话不多说了,来一起看看详细的介绍吧...
- Mysql 8.4数据库安装、新建用户和数据库、表单
-
1、下载MySQL数据库yuminstall-ywgetperlnet-toolslibtirpc#安装wget和perl、net-tools、libtirpcwgethtt...
- mysql5.7安装教程
-
首先下载mysql的rpm包wgethttps://mirrors.ustc.edu.cn/mysql-ftp/Downloads/MySQL-5.7/mysql-community-client...
- MySQL管理授权和数据库的备份和还原详解
-
一般管理用户和授权由DBA去执行,DBA为数据库管理员一、管理用户1.添加用户...
- 数据库迁移有什么技巧?|分享强大的database迁移和同步工具
-
概述DBConvertStudio是一款强大的跨数据库迁移和同步软件,可在不同数据库格式之间转换数据库结构和数据。它将成熟、稳定、久经考验的DBConvert和DBSync核心与改进的现代...
- Mysql解压版安装过程
-
Mysql是目前软件开发中使用最多的关系型数据库,具体安装步骤如下:第一步:Mysql官网下载最新版(mysql解压版(mysql-5.7.17-winx64)),Mysql官方下载地址为:https...
- MySQL5.7升级到8.0过程详解
-
前言:不知不觉,MySQL8.0已经有好多个GA小版本了。目前互联网上也有很多关于MySQL8.0的内容了,MySQL8.0版本基本已到稳定期,相信很多小伙伴已经在接触8.0了。本篇文章主要介绍从5....
- 10种常见的MySQL错误,你可中招?
-
【51CTO.com快译】如果未能对MySQL8进行恰当的配置,您非但可能遇到无法顺利访问、或调用MySQL的窘境,而且还可能给真实的应用生产环境带来巨大的影响。本文列举了十种MySQL...
- 忘记MySQL密码怎么办?一招教你搞定
-
在安装完MySQL或者是在使用MySQL时,最尴尬的就是忘记密码了,墨菲定律也告诉我们,如果一件事有可能出错,那么它一定会出错。那如果我们不小心忘记了MySQL的密码,该如何处理呢?别着急...
- Windows 安装解压版本的 MySql
-
1、下载解压版本的MySql到https://downloads.mysql.com/archives/community/网站,根据自己需要安装的版本进行选择下载,这里下载不要选择MSII...
- 爆破SSH/MySQL账户竟如此简单
-
友情提示:初入安全,小白一个,本文重在学习与经验分享!背景使用Kali自带的MSF工具对SSH的账号密码进行爆破。1.实验环境本次实验通过MSF,可直接对SSH的账号密码进行爆破。KaliIP:1...
- Mysql8忘记密码/重置密码
-
Mysql8忘记密码/重置密码UBUNTU下Mysql8忘记密码/重置密码步骤如下:先说下大概步骤:修改配置文件,使得用空密码可以进入mysql。然后置当前root用户为空密码。再次修改配置文件,不能...
- wamp查看MySQL密码 MySQL console输入密码闪退 重置mysql密码
-
wampserver的MySQL数据库用户名为root初始密码为空,但是部分同学通过MySQLconsole访问数据库输入密码的时候出现窗口闪退,常见的问题是原来有改过密码或者你的配置文件要求密码不...
- Mysql数据库操作指引(六)——账号密码及权限管理
-
简介:在MySQL数据库中,为了保证数据的安全性,数据管理员需要根据需要创建账户,并为每个账户赋予不同的权限,以满足不同用户的需求。...
- 一周热门
- 最近发表
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- dedecms模版 (53)
- c 视频教程下载 (33)
- listview排序 (33)
- firebug 使用 (31)
- characterencodingfilter (33)
- getmonth (34)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)
- android studio 3 0 (34)
- android应用开发 (31)
- html转js (35)
- 索引的作用 (33)
- checkedlistbox (34)
- localhost 8080 (32)
- 多态 (32)
- xmlhttp (35)
- mysql更改密码 (34)