-
1.
关于TS的类型系统
-
2.
将会用到的TS类型语法
-
2.1.
泛型
-
2.1.1.
定义了泛型的函数
-
2.1.2.
定义了泛型的类型别名(Type alias)
-
2.2.
元组与字符串模板类型
-
2.2.1.
元组(Tuple)
-
2.2.2.
字符串模板(Template Literal Types)
-
2.3.
类型推导与模式匹配
-
2.3.1.
extends关键字与条件类型(Conditional Types)
-
2.3.2.
使用infer搭配条件类型的extends
-
3.
使用类型系统的编程基础
-
3.1.
定义变量
-
3.1.1.
字面量
-
3.1.2.
联合类型
-
3.1.3.
剩下的常用于比较判断
-
3.1.4.
局部变量
-
3.2.
定义函数
-
3.3.
分支与判断
-
3.3.1.
字面量与字面量的比较
-
3.3.2.
字面量与集合比较
-
3.3.3.
传入联合类型
-
3.4.
循环与递归
-
3.4.1.
使用递归替代循环
-
3.4.2.
再来个例子
-
3.4.3.
递归深度限制与尾递归优化
-
3.5.
基础数学运算
-
3.5.1.
判断正负
-
3.5.2.
正整数加法
-
4.
实战类型体操
-
4.1.
FlattenDepth
-
4.2.
有更复杂的吗
-
5.
结语
-
6.
Reference
图灵完备
的,可以被拿来编程,网上称其为“类型体操(type gymnastics)”。这个词很有意思,虽然没有考据过来源,但是猜想这种编程和体操的性质很像——能锻炼技能但不实用。下文呢也是我接触学习后的一点总结,尽量从浅入深带大家了解一下。
TS由enhanced js + type system(类型系统)组成,其中编译后在.d.ts文件内的就是类型系统的代码了,所以对于类型系统编程完全可以在.d.ts内实现,只是需要给每条语句前加上
declare
关键字。为了简洁一点,本文的代码都是在.ts文件内的代码。
代码可以直接在typescript的
playground
里写,由于没有系统的IO接口,只能通过hover到类型名上看输出,如下图。
get output
这里
查看。
Conditional Types特性。该特性类似js里的三元表达式,通过extends
关键字进行前后类型的匹配。
匹配是看extends
前的类型是否是extends
后类型的子集,比如下面类型'猫'
不是类型'狗'
的子集。
type 叫声<动物 extends '狗' | '猫'> = 动物 extends '狗' ? '汪' : '喵';
type 叫一声 = 叫声<'猫'>; // '喵'
下面类型123
是类型number
的子集
type 类型<值 extends number | string> = 值 extends number ? '数字' : '字符串';
type 值类型 = 类型<123>; // '数字'
这里查看。
尾递归的优化,如果递归符合尾递归的写法递归深度就支持更多的层次。
这里将上面的Join
改成尾递归的写法,由于尾递归优化后程序不会保留函数上下文,需要在递归函数内手动传递,所以改造思路就是将结果字符串传给下一个递归函数,每个函数内将新的结果和传入的字符串拼接然后接着传。
type Join1<Arr extends string[], Separator extends string, Result extends string = ''> =
Arr extends [ExtractString<infer Head>, ...ExtractStringArray<infer Rest>] ?
Join1<Rest, Separator, `${Result}${Head}${Arr['length'] extends 1 ? '' : Separator}`> :
Result;
优化后深度可以达到1000左右了
type str3 = Join1<MakeArrayByLen<999>, ','>; // "0,0,0,.....,0,0"
题库,挑一道题来练练手吧。
这道题比较常规也比较实用就拿来写写吧。
如果是单层的Flatten会比较简单,只需要写一个之前提过的循环去遍历每一项判断是否是数组(item extends any[]
),如果是通过组合元组的...
语法与剩下项合并即可。
type Flatten<Arr extends any[]> =
Arr extends [infer Head, ...infer Rest] ?
[...Head extends any[] ? Head : [Head], ...Flatten<Rest>] :
这题需要支持多层flatten的话就需要不断去调用Flatten
函数直到: 1. 达到指定的level
2. 数组中已经没有需要flatten的元素
其中1比较好判断,可以设置一个计数器(实用数组实现,数组长度即为当前计数)每次递归累加,然后判断是否等于level
即可。2的话有多种方法,其中可以判断当前传入的数组是否arr extends number[]
,对于只会传入数字数组的这道题来说是ok的,不过如果想要更通用一点可以去比较Flatten后的数组是否和之前一样,这里我们用这种方法去实现题解。
type FlattenDepth<Arr extends any[], level extends number = 1, counter extends 0[] = []> =
counter['length'] extends level ?
Arr :
Flatten<Arr> extends Arr ?
Arr :
FlattenDepth<Flatten<Arr>, level, [...counter, 0]>;
虽然可以不用考虑性能,但是Flatten
虽然调用了两次还是可以优化的,大家可以尝试一下。(提示用之前提过的局部变量)
优化后的代码
中国象棋、Lisp解释器、BigInt加法等等,你也可以尝试一下。
Type Manipulation这章值得去看看。
最后提醒一下,请不要在生产代码中去写这些代码。