引言
最近准备开始刷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一起用于类型推断。
例如
// 实现keyof
type keyof<t> = t extends {[k in infer u]: unknown} ? u : never;
// 实现valueof
type valueof<t> = t extends { [k in keyof t]: infer u } ? u : never;
// 实现parameters和returntype
type 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 // error
obj["age"] // 20
obj['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]; // string a[1]; // string b[0]; // string b[1]; // number; b[0|2]; // string|boolean // 注意以下写法,为什么可以这么写,因为number是所有数字的集合 a[number]; // string b[number]; // string | number | boolean a["length"]; // number b["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>>;
至此,四则运算实现完毕。
以上就是typescript type类型使用梳理总结的详细内容,更多关于typescript type类型的资料请关注代码网其它相关文章!
发表评论