TypeScript 学习笔记 — 数组常见的类型转换操作记录(十四)
- 获取长度 length
type LengthOfTuple<T extends any[]> = T["length"];
type A = LengthOfTuple<["B", "F", "E"]>; // 3
type B = LengthOfTuple<[]>; // 0
- 取第一项 FirstItem
type FirstItem<T extends any[]> = T[0];
type A = FirstItem<[string, number, boolean]>; // string
type B = FirstItem<["B", "F", "E"]>; // 'B'
- 取最后一项 LastItem
type LastItem<T extends any[]> = T extends [...any, infer R] ? R : never;
type A = LastItem<[string, number, boolean]>; // boolean
type B = LastItem<["B", "F", "E"]>; // 'E'
type C = LastItem<[]>; // never
- 删除第一项 shift
type Shift<T extends any[]> = T extends [infer L, ...infer R] ? R : T;
type A = Shift<[1, 2, 3]>; // [2,3] . [never,2,3]
type B = Shift<[1]>; // []
type C = Shift<[]>; // []
- 向后追加 Push
type Push<T extends any[], K> = [...T, K];
type A = Push<[1, 2, 3], 4>; // [1,2,3,4]
type B = Push<[1], 2>; // [1, 2]
- 翻转 ReverseTuple
通过递归完成,先声明一个F
默认结果集,每次递归时,在将L
第一个值添加在F
展开之前,递归结束返回F
type ReverseTuple<T extends any[], F extends any[] = []> = T extends [infer L, ...infer R] ? ReverseTuple<R, [L, ...F]> : F;
type A = ReverseTuple<[string, number, boolean]>; // [boolean, number, string]
type B = ReverseTuple<[1, 2, 3]>; // [3,2,1]
type C = ReverseTuple<[]>; // []
- 拍平 Flat
通过递归完成,一个一个判断是否是数组,是数组继续 Flat,反之返回
type Flat<T extends any[], F extends any[] = []> = T extends [infer L, ...infer R]
? [...(L extends any[] ? Flat<L> : [L]), ...Flat<R>] // 判断第一个是否是数组
: [];
type A = Flat<[1, 2, 3]>; // [1,2,3]
type B = Flat<[[[[[[[1]]]]]], [2, 3], [4, [5, [6]]]]>; // [1,2,3,4,5,6]
type C = Flat<[]>; // []
type D = Flat<[1]>; // [1]
- 复制 Repeat
在 ts 中操作与数量相关的,全部采用元素的长度来计算
C`表示需要的长度,`F`是最终结果,`F["length"]`,结果的长度,两者都是字面量类型,只有相等的时候才能 extends,满足条件返回 F,不满足接着 Repeat,再次递归时,需要将上次的`F`展开放进去,再放入原始数据`T
type Repeat<T, C, F extends any[] = []> = C extends F["length"] ? F : Repeat<T, C, [...F, T]>;
type A = Repeat<number, 3>; // [number, number, number]
type B = Repeat<string, 2>; // [string, string]
type C = Repeat<1, 1>; // [1]
type D = Repeat<0, 0>; // []
- 过滤 Filter
type Filter<T extends any[], I, F extends any[] = []> =
// 先取出第一项,判断是否满足,满足递归处理R
T extends [infer L, ...infer R]
? // 无论如何都要遍历剩余项, 所以在F这判断 是否要放到数组中,使用[]包裹是为了防止any分发返回联合类型
Filter<R, A, [L] extends [A] ? [...F, L] : F>
: F;
type A = Filter<[1, "BFE", 2, true, "dev"], number>; // [1, 2]
type B = Filter<[1, "BFE", 2, true, "dev"], string>; // ['BFE', 'dev']
type C = Filter<[1, "BFE", 2, any, "dev"], string>; // ["BFE", "dev"] | ["BFE", any, "dev"]
- 查找索引 FindIndex
实现思路:
- 找到两个值相等的一项,思考如何判断两个值是否相等?
- 最终要返回的是索引:内部构建一个数组,来记录当前遍历到了第几项
type errIsEqual<T, U, Success, Fail> = [T] extends [U] // 类型 + 结构
? [U] extends [T]
? Success
: Fail
: Fail;
type xx = errIsEqual<any, 1, true, false>; // 测试判等方法:返回了 true,但是 any != 1
// 因此由于any类型的特殊性, 需要判断两个类型是否相等。需要严格判断 两者的 类型 + 结构
type isEqual<T, U, Success, Fail> = [T] extends [U] // 类型 + 结构
? [U] extends [T]
? keyof T extends keyof U
? keyof U extends keyof T
? Success
: Fail
: Fail
: Fail
: Fail;
type yy = isEqual<any, 1, true, false>; // 测试判等方法:返回了 false
type FindIndex<T extends any[], I, F extends any[] = []> = T extends [infer L, ...infer R]
? isEqual<L, I, F["length"], FindIndex<R, I, [...F, never]>>
: never;
type a1 = [any, never, 1, "2", true];
type a2 = FindIndex<a1, 1>; // 2
type a3 = FindIndex<a1, 3>; // never
- 元组转枚举 TupleToEnum
- 默认情况下,枚举对象中的值就是元素中某个类型的字面量类型,
- 第二个参数为 true,枚举对象中值的类型就是元素类型中某个元素在元组中的 index 索引,也就是数字字面量类型(需要借助上一个案例中
type FindIndex
)
type isEqual<T, U, Success, Fail> = [T] extends [U] // 类型 + 结构
? [U] extends [T]
? keyof T extends keyof U
? keyof U extends keyof T
? Success
: Fail
: Fail
: Fail
: Fail;
type FindIndex<T extends any[], I, F extends any[] = []> = T extends [infer L, ...infer R]
? isEqual<L, I, F["length"], FindIndex<R, I, [...F, never]>>
: never;
type TupleToEnum<T extends any[], I = false> = {
readonly [K in T[number]]: I extends true ? FindIndex<T, K> : K;
};
// 默认情况下
type a1 = TupleToEnum<["MacOS", "Windows", "Linux"]>;
// { readonly MacOS: "MacOS", readonly Windows: "Windows", readonly Linux: "Linux" }
// 第二个参数为true
type a2 = TupleToEnum<["MacOS", "Windows", "Linux"], true>;
// { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }
- 截取 slice
实现思路:
- 判断是否到达开始位置 S,到达就往结果集中添加,反之就舍弃
- 判断是否到达结束位置 E,没到就往结果集中添加,反之就返回
type Slice<
T extends any[],
S extends number, // 开始位置
E extends number = T["length"], // 结束位置
SA extends any[] = [], // 用于记录是否到达 S
EA extends any[] = [], // 用于记录是否到达 E
F extends any[] = [] // 结果集
> = T extends [infer L, ...infer R]
? SA["length"] extends S // 判断是否满足 开始index,满足放入集合F中
? EA["length"] extends E // 判断是否满足 结束index,表示截取完成
? [...F, L] // 都满足,直接结束,返回结果集 + L
: Slice<R, S, E, SA, [...EA, null], [...F, L]> // SA 匹配到索引就不在累积添加null
: Slice<R, S, E, [...SA, null], [...EA, null], F> // 添加null 是为了占位,撑开数组长度
: F;
type A1 = Slice<[any, never, 1, "2", true, boolean], 0, 2>; // [any,never,1] ---0到2
type A2 = Slice<[any, never, 1, "2", true, boolean], 1, 3>; // [never,1,'2'] ---1到3
type A3 = Slice<[any, never, 1, "2", true, boolean], 1, 2>; // [never,1] ---1到2
type A4 = Slice<[any, never, 1, "2", true, boolean], 2>; // [1,'2',true,boolean] ---2到结束
type A5 = Slice<[any], 2>; // [] ---2到结束
type A6 = Slice<[], 0>; // []
- 删除指定长度,并追加新的元素 splice
实现思路:
- 判断是否到达开始位置 S,到达就往结果集中添加,反之就舍弃并且 SA++
- 判断是否到达需要删除的个数,到达就拼接删除前的结果集 F + 需要添加的集合 I + 删除后剩下的集合,反之 DC++
type Splice<
T extends any[],
S extends number, // 开始index
C extends number, // 需要删除的个数
I extends any[] = [], // 需要插入的元素集合
SA extends any[] = [], // 用于记录是否到达 S
DC extends any[] = [], // 用于记录 已经删除的个数
F extends any[] = [] // 结果集
> = T extends [infer L, ...infer R]
? SA["length"] extends S // 判断是否满足 开始index,满足放入集合F中
? DC["length"] extends C // 判断是否满足 要删除的长度
? [...F, ...I, ...T] // 都满足,返回F删除前的结果集 + 需要添加的集合 + 删除后剩下的集合
: Splice<R, S, C, I, SA, [...DC, null], F> // 不满足删除的长度。DC++
: Splice<R, S, C, I, [...SA, null], DC, [...F, L]> // 不满足开始index。SA++
: F;
type A1 = Splice<[string, number, boolean, null, undefined, never], 0, 2>; // 从0开始删2个 [boolean,null,undefined,never]
type A2 = Splice<[string, number, boolean, null, undefined, never], 1, 3>; // 从1开始删3个 [string,undefined,never]
type A3 = Splice<[string, number, boolean, null, undefined, never], 1, 2, [1, 2, 3]>; // 从1开始删2个,添加另外3个 [string,1,2,3,null,undefined,never]
小总结
在 ts 中跟数量有关,全部会采用 length
凡是涉及遍历,全部采用递归[infer L,...infer R]来处理
['a','b','c'][number] 可以取到对应索引的 key 值
判断两个类型是否相等,需要考虑 any 类型,因此需要使用严格判等,类型 + 结构
TypeScript 学习笔记 — 数组常见的类型转换操作记录(十四)的更多相关文章
- Typescript 学习笔记六:接口
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记二:数据类型
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记七:泛型
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记五:类
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记四:回忆ES5 中的类
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记三:函数
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记一:介绍、安装、编译
前言 整理了一下 Typescript 的学习笔记,方便后期遗忘某个知识点的时候,快速回忆. 为了避免凌乱,用 gitbook 结合 marketdown 整理的. github地址是:ts-gitb ...
- TypeScript学习笔记(八):1.5版本之后的模块和命名空间
我之前有写过TS1.5版本之前的“模块”的笔记:TypeScript学习笔记(七):模块 但是TS这里的模块和在ECMAScript 2015里的模块(即JS原生支持了模块的概念)概率出现了混淆,所以 ...
- golang学习笔记16 beego orm 数据库操作
golang学习笔记16 beego orm 数据库操作 beego ORM 是一个强大的 Go 语言 ORM 框架.她的灵感主要来自 Django ORM 和 SQLAlchemy. 目前该框架仍处 ...
- Dart学习笔记-运算符-条件表达式-类型转换
Dart学习笔记-运算符-条件表达式-类型转换 一.运算符 1.算术运算符 + (加)- (减)* (乘)/ (除)~/ (取整) %(取余) 2.关系运算符 == (等等) != (不等) > ...
随机推荐
- 翻下旧资料,发现96年考过foxbase二级
翻下旧资料,找到 96年通过二级考试的证书,那时考的是Foxbase,一路走来,从最早用netware+dos无盘站+foxbase做订单系统,库存管理系统,再到使用记事本码asp网站,PB+orac ...
- js/css实现图片轮播
实现样式: tplb.css ul, li { padding: 0; margin: 0; list-style: none; } .adver { margin: 0 auto; width: 7 ...
- Linux 复制时排除某文件/目录
如果要排除/home/data目录下面的a.b.c.三个目录,同时拷贝其它所有目录,执行rsync命令yum install rsync -y #安装rsync 排除单个文件/目录rsync -avP ...
- 解决com.alibaba.excel.exception.ExcelGenerateException: Can not close IO.
我在使用easycel导出到zip包中时,出现了这个问题.各种文件输出时产生的问题其实大同小异 查看了一些网上的文章,还有github上关于此bug的issue,总算是理清并解决了. 解决方法一 主要 ...
- Java--接口和抽象类有什么区别
他们都不能实例化对象,都可以包含抽象方法,而且抽象方法必须被继承的类全部实现. 区别: 1.抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实 ...
- TP5.0--5.1获取当前域名的方法
TP5.0获取当前域名的方法 use think\Request; $request = Request::instance(); $domain = $request->domain(); 获 ...
- 手写一个简易的ajax
function ajax(url,successFul){ const xhr=new XMLHttpRequest() xhr.open("Get",url,true) xhr ...
- 前端程序员需要了解的MySQL
数据库的基本概念 数据库(database)是用来组织.存储和管理数据的仓库.对数据库中的数据可以进行增删改查操作.市面上常见的数据库有: MySQL(使用最广泛.流行度最高的开源免费数据库 Comm ...
- Spring Cloud Alibaba实现服务的无损下线功能
目录 1.背景 2.解决方案 2.1 找到通过负载均衡组件获取可用服务信息的地方 2.2 解决思路 3.部分实现代码 3.1 引入jar 3.2 编写服务下线方法 3.3 监听配置变更,清除服务缓存 ...
- k8s HPA(HorizontalPodAutoscaler)--自动水平伸缩
写在前面 我们平时部署web服务,当服务压力大撑不住的时候,我们会加机器(加钱):一般没有上容器编排是手动加的,临时加的机器,临时部署的服务还要改Nginx的配置,最后回收机器的时候,也是手动回收,手 ...