当个题目党,其实并不算太深刻
所在仓库地址:https://github.com/goblin-pitcher/steel-wheel-run/blob/master/typescript/type-challenge-pre-content.md
目标
最近筹备开始刷type-challenges
,因而先梳理一下ts类型相干知识点。
脱漏知识点总结
ts
的type合乎图灵齐备。这意味着ts
类型蕴含循环、判断等一系列基本操作。
类型汇合
类型该当作汇合来看,其中:
unknown
是选集,蕴含所有类型,是所有类型的父级- 之前在泛型中会写
Comp<T extends unknown>
看来是有些多此一举了...
- 之前在泛型中会写
never
是空集,是所有类型的子集
ts的设计合乎里氏替换准则,即子集能够替换所有父级,这意味着:
interface Parent { id: string;}// 合乎里氏替换准则,不会报错interface Child extends Parent { id: "childIdA"|"childIdB"; // "childIdA"|"childIdB"是string的子集 key: number; // Parent中没有key,该属性是Parent的拓展}// 不合乎里氏替换准则,报错interface ChildError extends Parent { id: number; // number不是string的子集}type A = {a: string};type B = {a: "A"|"a", b: number};let a: A = {a: "xxx"};let b: B = {a: "a", b: 2};// 合乎B是A的子集,该赋值合乎里氏替换准则, 反之 b=a 会报错a = b;
将TS的type看作编程语言
既然ts
的type是图灵齐备的,那么它天然能够实现所有计算,因而能够看作是一门语言
函数
在类型语境中,可将泛型看做是函数
type Fn<T> = T; // 看作 const Fn = val => val;
循环
常常能够在代码中看到如下写法:
type R = "A" | "B"type T = { [k in R]: k}
这里[k in R]
可看做循环for(const k in R){}
借助此个性,能够实现如下操作:
type MyPick<T, K extends keyof T> = { [k in K]: T[k]}type A = MyPick<{a: string, b: number, c: boolean}, "a"|"c">
当然这种循环只针对特定类型("a"|"b"
这种),略微简单点的循环还是得通过递归实现,例子的话,前面用ts type
实现四则运算时会用到。
条件语句
type
反对三元运算符,这个没什么好说的。。
须要留神的是,type
里没有等号,但能够用extends代替等号
type Equal5<T> = T extends 5 ? true : false;
来点练习
// eq1: 实现`GetProp<Obj, K>`(获取Obj[K]类型)type GetProp<Obj, K> = K extends keyof Obj ? Obj[K] : undefined;// eq2: 实现getName<User>type GetName<User> = GetProp<User, "name">
extends
也常和infer
一起用于类型推断。
例如
// 实现KeyOftype KeyOf<T> = T extends {[k in infer U]: unknown} ? U : never;// 实现ValueOftype ValueOf<T> = T extends { [k in keyof T]: infer U } ? U : never;// 实现Parameters和ReturnTypetype FuncBase<F, C extends "params"|"return"> = F extends (...params: infer P) => infer R ? (C extends "params" ? P : R) : never;type Params<F> = FuncBase<F, "params">;type Return<F> = FuncBase<F, "return">;
赋值
赋值局部参看后面类型汇合章节,赋值要遵循里氏替换准则。
条件语句章节提到了infer
,或者能够用infer
实现解构赋值。
// 须要实现相似js的const {name, ...extra} = user,求extra。(其实就是Omit办法)type MyPick<Obj, T extends keyof Obj> = {[k in T]: Obj[k]};type MyExclude<Obj, T extends keyof Obj> = keyof Obj extends T|(infer U extends keyof Obj) ? U : never;type MyOmit<Obj, T extends keyof Obj> = MyPick<Obj, MyExclude<Obj, T>>;// 实现数组的解构赋值const [A, ...extra] = arr;type GetArrBase<T extends unknown[], C extends "first"|"rest"> = T extends [infer First, ...infer Rest] ? (C extends "first"?First: Rest): never;type GetFirst<T extends unknown[]> = GetArrBase<T, "first">;type GetRest<T extends unknown[]> = GetArrBase<T, "rest">;
对象
type
的对象能够类比js
中的对象,应用办法如下,留神最初一个例子
type Obj = { name: string; age: 20;}Obj["name"] // string;Obj.age // ErrorObj["age"] // 20Obj['name'|'age'] // string | 20 , 这个个性很重要!!!
利用这个个性,能够实现如下性能
interface Test { a: string; b: number; c: boolean;}// 之前不晓得这个个性时,用infer也能达到同样的成果,但实现不如这个直观type ValueOf<T> = T[keyof T];type R = ValueOf<Test>;
数组
ts
中的数组分为Array
数组和Tuple
元组。
Array
数组是诸如string[]
的写法,相似java
或其余语言的数组。
Tuple
元组更像是js
中的数组,写法是[string, number, boolean]
这种。
(注:js
中不存在真正意义上的数组,数组是在内存上开拓间断空间,每个单元格所占内存都一样,在js
中,数组写成['a', 5555, {a: 1}]
都没问题,显然在实现上不是真正的开拓了间断内存空间,应该是用链表模仿的,为了解决链表自身查问慢的问题,应该是采纳了跳表或者红黑树的形式组织的?)
数组中须要留神的点如下:
type A = string[];type B = [string, number, boolean];// =========================分割线==========================// 重点留神!!!A[0]; // stringA[1]; // stringB[0]; // stringB[1]; // number;B[0|2]; // string|boolean// 留神以下写法,为什么能够这么写,因为number是所有数字的汇合A[number]; // stringB[number]; // string | number | booleanA["length"]; // numberB["length"]; // 3// ts数组同样能够像js数组那样开展type Spread<T extends unknown[]> = [...T]Spread<[1,2,3]> // [1,2,3]
依据以上个性,很容易实现以下练习:
// eq1: 实现 `ItemOf`办法(获取数组中项的类型)type ItemOf<T extends unknown[]> = T[number];// 之前不晓得这个个性时,用infer实现的代码如下type ItemOfByinfer<T> = T extends (infer N)[] ? N : never;// eq2:实现`Append`办法type Append<T extends unknown[], Ele> = [...T, Ele];// eq3: 实现返回数组length+1// ts尽管无奈实现加减运算,但能够通过模仿生成对应新类型,返回其属性,从而模仿加减运算type LengthAddOne<T extends unknown[]> = [unknown, ...T]["length"];
四则运算
运算加减依赖于元组长度,因而先定义一些根本办法,留神..因为是依赖元组长度,因而无奈算正数和小数,只能算正整数...
(注:尽管无奈计算正数和小数,但ts的type仍旧是图灵齐备的,位运算也只是01的运算,正数和小数都是一堆01的定义,比方把10000看做0,且最初两位是小数,那么9999就是 -0.01)
// 返回Push后的数组type Append<T extends unknown[], E = unknown> = [...T, U];// 同理,返回Pop后的数组代码如下,临时用不到// type RemoveTop<T extends unknown[]> = T extends [...(infer U), unknown] ? U : never;type Tuple<N extends number, Arr extends unknown[] = []> = Arr["length"] extends N ? Arr : Tuple<N, Append<Arr>>
有了这些根本办法,先实现加法和减法
type Add<A extends number, B extends number> = [...Tuple<A>, ...Tuple<B>]["length"];type Subtract<A extends number, B extends number> = Tuple<A> extends [...Tuple<B>, ...(infer U)] ? U["length"] : never;
乘法的话,A*B
就是A
个B
相加,简易版乘法如下,思路不难,但间接用Add和Subtract封装,很多写法都提醒嵌套太深。。
留神,这里用于统计和的参数S以元组示意,因为所有运算都是以元组为基准,S用数字示意会先转元组再转数字,来来回回开销比拟大。
type MultipleBase<A extends number, B extends number, S extends unknown[] = []> = B extends 0 ? S["length"] : MultipleBase<A, Subtract<B, 1>, [...S, ...Tuple<A>]>;
乘法还有优化的空间,例如2*100
,间接用这个算的是100个2相加,工夫复杂度不如100*2
,而计算这么优化的前提是,实现BiggerThan
办法。
type BiggerThan<A extends number, B extends number> = Tuple<A> extends [...Tuple<B>, ...infer U] ? (U["length"] extends (never|0) ? false : true): false;// 优化后的乘法如下type Mutiple<A extends number, B extends number> = BiggerThan<A, B> extends true ? MultipleBase<A, B> : MultipleBase<B, A>;
有了BiggerThan
,除法也好说,例如a/b
,断定b*2、b*3...b*n
和A的大小就行。
同乘法的实现,用于统计的参数R为元组。。
type Divide<A extends number, B extends number, R extends unknown[] = []> = BiggerThan<B, A> extends true ? R["length"] : Divide<Subtract<A,B>, B, Append<R>>;
至此,四则运算实现结束。