TypeScript自测清单
一、核心概念
-
TypeScript的火其实是一种必然,因为随着JavaScript的发展越来越好, 许多大型项目都需要使用JavaScript来进行来发,但是JavaScript是动态的语言,容易在运行时产生一些难以修复的 Bug,因此急需一个静态的类型语言;这就是为什么TypeScript现在会出现并且火起来了。 -
TypeScript是可以进行类型编程的,属于图灵完备的语言 -
元组就是元素个数和类型都确定的数组类型;
Tuple -
接口可以用来描述函数、构造器、索引类型(对象、class、数组)等复合类型
-
特殊类型
- void 代表空,可以是 null 或者 undefined,一般是用于函数返回值。
- any 是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never)。
- unknown 是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型
- never 代表不可达,比如函数抛异常的时候,返回值就是 never。
-
infer 可以推断 某个类型的一部分
-
映射类型
- 索引查询
keyof T - 索引遍历
K in keyof T - 索引访问
T[K]
- 索引查询
-
索引变化(也就是 key 也可以发生变化, 使用
as,重映射)type MapType<T> = { [Key in keyof T as `#${Key & string}`]: T[Key]; }; -
提取
// 使用类型体操去除字符串左右的空字符 type TrimRight<Str extends string> = Str extends `${infer Use}${' '|'\n'|'\t'}` ? TrimRight<Use> : Str type TrimLeft<Str extends string> = Str extends `${' '|'\n' | '\t'}${infer Use}` ? TrimLeft<Use> :Str type Trim<Str extends string> = TrimRight<TrimLeft<Str>>TypeScript类型的模式匹配是通过类型 extends 一个模式类型,把需要提取的和部分放到通过 infer 声明的局部变量里,后面可以通过这个局部变量拿到类型做各种后续的处理。
-
重构
-
重构的含义是指:将原来某种类型进行拆分组装,形成一个新的类型;看下面这个例子;
// 实现一个可以添加一个类型的类型; type Push<BaseArr extends unknown[], Ele> = [...BaseArr, Ele]; // 这个Push类型就具备有添加一个元素的能力了,并且构建了一个新的类型;这个就是重构; -
函数类型的重构
// 给传过来的某一个函数类型的参数进行重构 type AppendArgument<Fun extends Function , Arg> = Fun extends (...args:infer Args)=>infer ReturnType ? (...args:[...args,Arg])=>ReturnType : never -
索引类型的重构
// 过滤一个索引类型,根据固定的值将其过滤掉 type FilterKey<Base extends object, value> = { [K in keyof Base as K extends value ? never : K]: Base[K]; }; // 这样既可以将包含在value中的给过滤掉;实际上omit就是这个作用
-
-
递归复用
-
promise 的递归
// 写一个类型:可以取出深层次 Promise的值、 type DeepPromiseValue<T> = T extends Promise<infer Value> ? DeepPromiseValue<Value> : T; -
翻转数组
// 翻转一个数组类型 type Reverse<Arr extends unknown[]> = Arr extends [ infer First, ...infer Rest ] ? [...Reverse<Rest>, First] : Arr; -
构建数组
// 构建一个数组 告诉我长度就行 type BuildArr< Length extends number, Ele = unknown, Arr = [] > = Arr["length"] extends Length ? Arr : BuildArr<Length, Ele, [...Arr, Ele]>; -
在类型体操中,遇到数量不确定的问题,要条件反射的想到递归。
-
-
数组长度
-
基于构建数组实现加法
type Add<One extends number, Two extends number> = [ ...BuildArr<One>, ...BuildArr<Two> ]["length"]; // 需要基于 BuildArr来进行实现; -
实现减法
type SubTract< One extends number, Two extends number > = BuildArr<One> extends [...infer Rest, ...BuildArr[Two]] ? Rest["length"] : never;
-
-
联合分散
-
当类型参数为联合类型,并且在条件类型左边直接引用该类型参数的时候,TypeScript 会把每一个元素单独传入来做类型运算,最后再合并成联合类型,这种语法叫做分布式条件类型
type Add<T> = T extends string ? `${T}#` : never; type Test = Add<"a" | "b">; -
对联合类型的处理和对单个类型的处理没什么区别,TypeScript 会把每个单独的类型拆开传入。不需要像数组类型那样需要递归提取每个元素做处理。
-
isUnion 的实现
type isUnion<A, B = A> = A extends A ? [B] extends [A] ? false : true : never;当 A 是联合类型的时候:A extends A 这种写法是为了触发分布式条件类型,让每个类型单独传入处理的,没别的意义。
- 将数组类型变为联合类型
type ToUnion<T extends unknown[]> = T[number];- BEM 练习
type BEM< Block extends string, Element extends string[], Modifiers extends string[] > = `${Block}__${Element[number]}--${Modifiers[number]}`; type BemResult = BEM<"song", ["xiao", "da"], ["peng", "yong"]>; -
-
特殊类型的特征
- IsAny any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any。
type IsAny<T> = "string" extends "string" & any ? true : false;- IsEqual
type IsEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends < T >() => T extends B ? 1 : 2 ? true : false;-
IsNever:never 在条件类型中也比较特殊,如果条件类型左边是类型参数,并且传入的是 never,那么直接返回 never.
type TestNever<T> = T extends string ? true : false; type Test = TestNever<never>; // never正因为如此我们才需要使用下面的方式,使用中括号包起来,使其失去上面的特性;
type IsNever<T> = [T] extends [never] ? true : false; -
isTuple
我们之前提到过,元组就是数组的元素类型和长度都确定的数组而已,因此他们都具有
length这个属性,因此当我们去访问他们的时候,会有不一样的体现方式;type TupleLength = [1, 2, 3]["length"]; // 3 type ArrLength = number["length"]; // number可以得到一个结果,那就是元素的 length 属性是一个具体的值类型,但是数组的 length 属性是一个不具体的 number 类型;利用这个点,实现下面这个效果;
type isTuple<T> = T extends readonly [...params:infer Eles] ? NotEqual<Eles['length'] , number> : false type NotEqual<A , B> = (<T>()=> T extends A ? 1 : 2) extends (<T>()=> T extends B ? 1 :2) ? false : true -
过滤可选索引
一个小知识点
type TestA = {} extends { name: string } ? true : false; // false type TestB = {} extends { name?: string } ? true : false; // true一个空对象类型可以分配给一个可选(也就是有可能会有一种情况让这个类型变为一个空对象类型),但不可以分配给一个不可能成为空对象类型的类型;
于是,就可以做下面这样的判断
type GetOptional<Obj extends Record<string, any>> = { [Key in keyof Obj as {} extends Pick<Obj, Key> ? Key : never]: Obj[Key]; };- keyof 只能拿到 class 的 public 索引,private 和 protected 的索引会被忽略。
-
函数重载
- 函数重载的几种方式
// 方式一 declare function Add(a:number , b:number)=>number declare function Add(a:sring , b:string)=>string // 方式二 interface Add { (a:number , b:number)=>number (a:sring , b:string)=>string } // 方式三 type Add = (a:number , b:number)=>number & (a:sring , b:string)=>string
二、案例实战
- 手写 paramString
需求:接受一个字符串类型,形如a=1&b=2这样的类型,将其转化为{a:1 , b:2}这样的类型
const str = "a=1&b=2&c=3";
// 实现函数
function parse(str: string): object {
const itemArr = str.split("&");
const res = {};
itemArr.forEach((item) => {
const [key, value] = item.split("=");
if (res[key]) {
if (Array.isArray(res[key])) {
res[key] = [...res[key], value];
} else {
res[key] = [res[key], value];
}
} else {
res[key] = value;
}
});
return res;
}
console.log(parse(str));
// 定义类型
type ParseItem<Str extends string> = Str extends `${infer Key}=${infer Value}`
? Record<Key, Value>
: {};
type MergeItem<A, B> = {
[Key in keyof A | keyof B]: Key extends keyof A
? Key extends keyof B
? A[Key] extends unknown[]
? B[Key] extends unknown[]
? [...A[Key], ...B[Key]]
: [...A[Key], B[Key]]
: [A[Key], B[Key]]
: A[Key]
: Key extends keyof B
? B[Key]
: never;
};
// type MergeValue<A , B> = [A , B]
type ParseStr<Str extends string> =
Str extends `${infer OneItem}&${infer OtherItem}`
? MergeItem<ParseItem<OneItem>, ParseStr<OtherItem>>
: ParseItem<Str>;
type Test1 = ParseStr<"a=1&b=2&c=3&a=2">;
type Test2 = ParseItem<"a=3">;
- 将
aaa-bbb-ccc转为aaaBbbCcc这样的类型
- 补充一个知识点:内置类型
Capitalize可以让传入的首字母大写
type Test = Capitalize<"aaa">; // Aaa
type KebabCaseToCameCase<Str extends string> =
Str extends `${infer First}-${infer Rest}`
? `${First}${KebaCaseToCameCase<Capitalize<Rest>>}`
: Str;
// test
type Test = KebabCaseToCameCase<"aaaa-bbb-ccc">; // type Test = "aaaaBbbCcc"
- 将
aaaBbbCcc转为aaa-bbb-ccc这样的类型
type CameCaseToKebabCase<Str extends string> =
Str extends `${infer First}${infer Rest}`
? First extends Lowercase<First>
? `${First}${CameCaseToKebabCase<Rest>}`
: `-${Lowercase<First>}${CameCaseToKebabCase<Rest>}`
: Str;
// test
type Test = CameCaseToKebabCase<"aaaBbbCcc">; // type Test = ""aaa-bbb-ccc"
- 实现一个类型,将
[1,2,3,4,5,6 ....]转变为[[1,2], [3,4], [5,6] ....]
type Chunk<
Arr extends unknown[],
ItemLen extends number,
CurItem extends unknown[] = [],
Res extends unknown[] = []
> = Arr extends [infer First, ...infer Rest]
? CurItem["length"] extends ItemLen
? Chunk<Rest, ItemLen, [First], [...Res, CurItem]> // 说明CurItem上限已到
: Chunk<Rest, ItemLen, [...CurItem, First], Res>
: [...Res, CurItem];
// test
type Test = Chunk<[1, 2, 3, 4, 5, 6, 7, 8], 2>; // type Test = [[1, 2], [3, 4], [5, 6], [7, 8]]
-
将联合类型转为元组类型 将 "a" | "b" | "c" 转为 ["a" , "b" , "c"]
- 知识点一:如何讲一个联合类型转为交叉类型
实际上,联合类型在一般的运算当中,都是协变的;也就是说联合类型在进行构建的时候,会将联合类型的每一个元素传入做运算再联合起来;但是如果使用函数参数的话,这种现象会被破坏,结果将不再是所有的结果联合,,这里就是因为函数参数是逆变的,会返回联合类型的几个类型的子类型,也就是更具体的交叉类型。
type UnitToIntersection<U> = ( U extends U ? (x: U) => unknown : never ) extends (x: infer R) => unknown ? R : never; type UnionToFunIntersection<T> = UnitToIntersection< T extends any ? () => T : never >;- 实现:
type UnionToTuple<T> = UnionToIntersection< T extends any ? () => T : never > extends () => infer ReturnType ? [...UnionToTuple<Exclude<T, ReturnType>>, ReturnType] : [];
三、鸣谢
本来这篇文章是不打算发表的,但是想一想,这些笔记可能对看的伙伴还是有帮助的,就好好整理了一下,在这里要特别感谢zxg_神说要有光大佬写的小册,从这本书当中我真的收获很多,也以你为榜样,希望有一天也能和你一样写出高质量有价值的文章来;
欢迎评论区讨论TypeScript的问题,一起努力;