TypeScript体操,手动实现一些TS语法
前言
TypeScript是JavaScript的超集,TypeScript 通过对原生 JavaScript 提供强类型加持,在很大程度上提升了代码质量。但我在刚开始使用TS的时候,真的太难受了。曾经的我爱上前端就是因为前端开发起来方便快捷,JS的类型可以随心所欲,HTML写完立马就能看到效果。而现在,来了TS,边写边学,边学边写,工作量几近与翻倍。
但是!同志,请相信我,认真对待写TS这件事,你将收获:
0.更好友好的开发体验,提示更友好
1.TS声明即文档,减少了查文档的时间
2.复杂的大型项目,能够保证数据结构层级不错位
3.更好的抽象能力,部分枚举、模型和后端保持一致
4.规避低级错误,比如类型错误、函数传参错误、边界情况的判断
为什么要做TypeScript体操?
TS本身是一门语言,有自己的语法。从JS过来的前端工程师,往往都是瞄几眼TS官方文档就上手了,保证基本的日常开发没有问题就觉得掌握了TS这门语言的奥秘。但是在不经意间打开xx.d.ts
的时候,尴尬的发现自己并看不懂别人写的TS声明文件,面试官让你手写一个TS的语法糖,你总是支支吾吾,好像会,又好像不太会。所以,希望通过这次的分享,希望可以达到以下几个目的:
0.从源码的角度理解每一个工具类型的实现机制
1.加深对TypeScript的理解
2.能够在平时的工作中举一反三,利用TypeScript为我们的代码保驾护航
关键字解释
typeof
在 TS 中用于类型表达时,typeof
可以用于从一个变量上获取它的类型。
const func = (a: number, b: number): number => {
return a + b;
}
const obj = {
name: 'zaoren',
age: 11
}
type tupeFunc = typeof func; // type tupeFunc = (a: number, b: number) => number
type obj = typeof obj; // type obj = { name: string; age: number;}
keyof
keyof
的作用将一个 类型 映射为它 所有成员名称的联合类型
// 用 TS interface 描述对象
interface TsInterfaceObject {
first: string;
second: number;
}
// 用 TS type 描述对象
type TsTypeObject = {
first: string;
second: number;
};
// 用 Ts type 描述基本类型别名
type TsTypeAlias = string;
class JsClass {
private priData: number;
private priFunc() {}
public pubData: number;
public pubFunc1() {}
public pubFunc2() {}
}
type NameUnionOfInterface = keyof TsInterfaceObject;
/**
* 将所描述对象中的所有 key 组合成一个联合类型
* type NameUnionOfType = "first" | "second"
*/
type NameUnionOfTypeObj = keyof TsTypeObject;
/**
* 对一个非对象类型使用 keyof 后,会返回其 prototype 上所有 key 组合成的一个联合类型
* type NameUnionOfTypeAlias = number | typeof Symbol.iterator | "toString" | "charAt" | ...
*/
type NameUnionOfTypeAlias = keyof TsTypeAlias;
/**
* 其实也是返回 prototype 上所有 key(因为其实 private 私有部分实际是放在实例化对象中,而非原型)
* type NameUnionOfClass = "pubData" | "pubFunc1" | "pubFunc2"
*/
type NameUnionOfClass = keyof JsClass;
in
in 可以按照js中的for…in遍历去理解,我们拿Pick这个语法糖来具体举例:[p in K]
相当于就在遍历'name' | 'age'
属性。
interface Person {
name: string;
age: number;
location: string;
}
// 从 Person 中取出 'name' 属性
type pPersion = Pick<Person, 'name' | 'age'> // type pPersion = {name: string; age: number }
/**
* Pick 的语法
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
extends
extends关键字在TS编程中出现的频率挺高的,而且不同场景下代表的含义不一样
-
类型继承,类型 A 去继承类型 B(
interface
可用extends
继承,type
不可以)interface IHuman { gender: "male" | "female"; age: number;}interface IProgrammer { language: "java" | "php" | "javascript";}/** * interface 的继承 * 可以同时继承多个 interface,它们之间用逗号分隔 */interface IWebDeveloper extends IProgrammer, IHuman { skill: "vue" | "react";}const Me: IWebDeveloper = { skill: "react", language: "javascript", age: 18, gender: "male",};
* 对类型进行条件约束在书写泛型的时候,我们往往需要对类型参数作一定的限制,比如希望传入的参数都有 name 属性的数组我们可以这么写:function getCnames<T extends { cname: string }>(entities: T[]):string[] { return entities.map(entity => entity.cname) }
这里extends
对传入的参数作了一个限制,就是 entities 的每一项可以是一个对象,但是必须含有类型为string
的cname
属性。 -
条件匹配
// 判断范型 T 是否匹配于 numbertype OnlyWantNumber<T> = T extends number ? any : never;type Type1 = OnlyWantNumber<number>; // type Type1 = anytype Type2 = OnlyWantNumber<string>; // type Type2 = never
需要注意的是,如果extends
左侧的类型为联合类型,会被拆解,就像数学中的分解因式子一样`P<‘x’ | ‘y’> => P<‘x’> | P<‘y’>````type P = T extends ‘x’ ? string : number; type A3 = P<‘x’ | ‘y’> // A3的类型是 string | number ```#### infer
infer
。它可以在 extends 关键字右侧的类型表达式中的任何位置使用。使用它可以为出现在该位置的任何类型命名
// 这里的 extends 在描述泛型
type Unpack<A> = A extends Array<infer E> ? E : A
type Test = Unpack<Apple[]> // => Apple
type Test = Unpack<Apple> // => Apple
Partial
使用Partial将T中所有的属性都变为非必须的
用法
type Foo = {
a: string
b: number
c: boolean
}
const a: MyPartial<Foo> = {}
实现
1.首先使用 keyof T
回 T 类型的所有键组成的一个联合类型 ,Foo类型则返回 string | number | boolean
2.使用in
语法来遍历所有的key
3.将所有的属性改为非必须
type MyPartial<T> = {
// 1. keyof T 会返回 T 类型的所有键组成的一个类型
// 2. in 有点类似于 for...in 的语法。遍历所有的key[P in keyof T] ?: T[P]
}
Required
将类型T中的所有属性都变为必须的
用法
type Foo = {
a?: string
b?: number
c?: boolean
}
const a: MyRequired<Foo> = {}
// Erro