基本介绍
TypeScript是一种开源的编程语言,给 JavaScript 增加了一套静态类型系统。通过 TS Compiler 编译为 JS,编译的过程做类型检查。
因为代码中添加了静态类型,也就可以配合编辑器来实现更好的提示、重构等,这是额外的好处。
TypeScript 的类型系统是图灵完备的,也就是能描述各种可计算逻辑。简单点来理解就是循环、条件等各种 JS 里面有的语法它都有,JS 能写的逻辑它都能写。
因为TypeScript支持字面量类型,并且支持逻辑运算,因此我们可以在此基础上做类型编程。
关于TypeScript的基础知识可以参考:jkchao.github.io/typescript-…
基本运算符
条件判断 extends ? :
// 判断这个类型是不是2
type isTwo<T> = T extends 2 ? true: false;
推导:infer
如果我们的不断深入,需要引入推导 infer 关键字,表示在 extends 条件语句中待推断的类型变量。
// 提取类型infer
// 推断初第一个元素的类型是C
type ArrFirst<T extends unknown[]> = T extends [infer C, ...infer R] ? C : never;
这里是将数组的类型解构,并且把数组的第一个的类型赋值为C,而这里的extends ?:就类似于if判断语句。同样的道理我们可以获取数组的最后一个
infer ,我的理解相当于创建了一个类型的名称,去存储一个类型。
联合:|
联合类型(Union)类似 js 里的或运算符 |,但是作用于类型,代表类型可以是几个类型之一。
`type Union = ``1` `| ``2` `| ``3``;`
交叉:&
交叉类型(Intersection)类似 js 中的与运算符 &,但是作用于类型,代表对类型做合并
`type` `ObjType = {a: number } & {c: boolean};`
映射类型
对象 class 在typescript对应的类型是索引类型,如果我们想要修改,就需要映射类型。
type MapType<T> = {
[Keyin keyof T]?: T[Key]
}
- keyof T 是查询索引类型中所有的索引,叫做
索引查询。 - T[Key] 是取索引类型某个索引的值,叫做
索引访问。 - in 是用于遍历联合类型的运算符。
比如我们把一个索引的值变为三个元素的数组
type MapType<T> = {
[Key in keyof T]: [T[Key], T[Key], T[Key]]
}
type res = MapType<{a: 1, b: 2}>;
尝试深入进阶
有了以上的基础之后,我们可以稍微一些进阶一点的内容
获取Promise包裹的返回值
type GetValueType<P> = P extends Promise<infer Value> ? Value : never;
type res = GetValueType<Promise<number>>
如果传入的泛型P,是一个Promise的话,我们就把Promise里面的值的类型推导为Value,并且返回Value;否则的话就返回never。
获取函数的参数类型
`type` `GetParameters<Func extends Function> =`
` ``Func extends (...args: infer Args) => unknown ? Args : never;`
获取函数的返回类型
同样的思路,我们可以实现函数的返回类型
type getReturnType<Func extends Function> =
Func extends (...args: infer Args) => infer T ? T : never;
type res = getReturnType<(name: string) => () => {}>
删除数组中的最后一个元素
掌握了infer的思想,我们可以用解构赋值的思路去实现删除一个元素
type PopArr<Arr extends unknown[]> =
Arr extends [] ? []
: Arr extends [...infer Rest, unknown] ? Rest : never;
如果传入的类型是个空的数组,我们就直接返回空数组;不然的话如果是一个非空的数组,我们就将前面length-2的部分解构到Rest,后面的一个元素用unknown占位,再返回Rest,否则返回never。
判断字符串的开头
除了常规的字符串的操作之外,我们还可以对字符串进行处理
我们仍然可以利用这种infer的匹配的方式
type StartsWith<Str extends string, Prefix extends string> =
Str extends `${Prefix}${string}` ? true : false;
将字符串和前缀输入,判断Str是否是匹配Prefix{string}这样的字符串,前面的Prefix代表前缀,后面的string代表任意字符串
字符串的替换
我们已经可以实现将字符串进行分别的匹配,换言之我们可以拿到不同的部分,因次我们将源字符串进行拆解之后重新拼装
type ReplaceStr<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${Suffix}` : Str;
现将字符串进行extends判断,将字符串的前缀,替换字符、后缀分别赋值,在重新构造字符串
字符串替换–递归
在常规的js运算中我们可以使用递归来做多次处理,ts中也是一样的
type ReplaceStr<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
? ReplaceStr<`${Prefix}${To}${Suffix}`, From, To> : Str;
type res1 = ReplaceStr<'abcdabcdabcd', 'd', 'f'>
我们将从新拼接的字符串重新作为入参进行递归判断,直到不满足extends则直接返回Str。
字符串的进行首字母大写
我们要调用首字母大写,就需要先匹配到第一个字母,将字母转换之后再拼接返回
type CapitalizeStr<Str extends string> =
Str extends `${infer First}${infer Rest}`
? `${Uppercase<First>}${Rest}` : Str;
我们取出字符串的第一个字符,再用Uppercase去将第一个变化为大写,最后返回拼接的字符串。
这里我们用的字符串的匹配,如果没有任何的分隔符,我们将会匹配到字符串的第一个字母。
\
CamelCase
我们来实现 dong_dong_dong 到 dongDongDong 的变换
type CamelCase<Str extends string> =
Str extends `${infer Left}_${infer Right}${infer Rest}`
? `${Left}${Uppercase<Right>}${CamelCase<Rest>}`
: Str;
我们来讲讲这其中关于字符串的匹配
拿到一个字符串,先用分隔符"_"去匹配,Left则是“dong”,然后再对右侧去匹配Right指的是第一个字母“d”,Rest则是后面。因为涉及到很多元素,因此需要递归
多说一句
比如我们查看Uppercase的实现,发现是intrinsic,intrinsic代表了这些工具类型是由TS编译器内部实现的。
计数思路
在TypeScript中,我们可以利用数组的length属性做计数
type num1 = [unknown]["length"];
type num2 = [unknown, unknown]["length"];
构建指定长度的数组
type BuildArray<
Length extends number,
Ele = unknown,
Arr extends unknown[] = []
> = Arr["length"] extends Length ? Arr : BuildArray<Length, Ele, [...Arr, Ele]>;
我们为Arr默认值[],通过判断length来判断是否返回数组,不然的话就递归调用,再为数组添加元素[...Arr, Ele]
地址
数字加法
我们已经实现了构建指定的数组,那么我们就需要购将这两个数组,再合并起来,读取length
type Add<Num1 extends number, Num2 extends number> = [
...BuildArray<Num1>,
...BuildArray<Num2>
]["length"];
数字减法
type Subtract<
Num1 extends number,
Num2 extends number
> = BuildArray<Num1> extends [...BuildArray<Num2>, ...infer Rest]
? Rest["length"]
: never;
type SubtractRes1 = Subtract<100, 20>;
数字减法的思路是我们构建数字较大的数组(被减数),再去匹配一个一模一样的数组(减数),减数的数量不够的话肯定需要一定长度的数组去匹配,这样读出Rest的长度即可
数字乘法
乘法就是加法的简便运算,原来一个一个加,现在变为了n个n个的加。比如m*n,其实就是n个m相加,这里的话就是一次一次的添加m个元素,重复n次。
// 递归做乘法
type Multiplication<
Num1 extends number,
Num2 extends number,
Result extends unknown[] = []
> = Num2 extends 0
? Result["length"]
: Multiplication<Num1, Subtract<Num2, 1>, [...BuildArray<Num1>, ...Result]>;
type MultiplicationRes = Multiplication<100, 20>;
数字除法
除法和乘法想法,其实就是n个n个的减,每减一次,结果加1,直到减为0。
// 递归做除法
type Division<
Num1 extends number,
Num2 extends number,
Result extends unknown[] = []
> = Num1 extends 0
? Result["length"]
: Division<Subtract<Num1, Num2>, Num2, [unknown, ...Result]>;
type DivisionRes = Division<20, 4>;
结语
经过以上的举例,我们已经有了有了一定的类型编程能力,可以进行更多锻炼。我们可以看看Pick Partial Omit等内部的实现,发现其实并不复杂。
Github上面有个仓库,里面有很多学习的案例,可以更深层次学习下。
author:@念歌