前⾔
前⾔:这⾥的标题看起来是 "⾼级⽤法",不少同学可能就表⽰被劝退了。其实 TypeScript 作为⼀门 强类型 编程语⾔,最具特⾊的就是他的类型表达能⼒,这是很多完备的后端语⾔都难以媲美的 ~~说的很对,但PHP是最好的语⾔~~,所以如果你搞懂了他的类型系
基础准备
预备知识
本⽂的定位为理解⾼级⽤法,故不会涉及过多基础知识相关的讲解,需要读者⾃⼰去完善这⽅⾯的知识储备。
此⽂档的内容默认要求读者已经具备以下知识:
1. 有 Javascript 或其他语⾔编程经验。
2. 有 TypeScript 实际使⽤经验,最好在正经项⽬中完整地使⽤过。
3. 了解 TypeScript 基础语法以及常见关键字地作⽤。
4. 对 TypeScript 的 类型系统 架构有⼀个最基本的了解。
相关资源推荐
1. TypeScript 官⽹
2. TypeScript Deep Dive
3. TypeScript GitHub地址
背景
初⽤ TypeScript 开发的同学⼀定有这样的困扰:
1. 代码代码提⽰并不智能,似乎只能显式的定义类型,才能有代码提⽰,⽆法理解这样的编程语⾔居然有这么多⼈趋之若鹜。
2. 各种各样的类型报错苦不堪⾔,本以为听信⽹上说 TypeScript 可以提⾼代码可维护性,结果却发现徒增了不少开发负担。
3. 显式地定义所有的类型似乎能应付⼤部分常见,但遇到有些复杂的情况却发现⽆能为⼒,只能含恨
写下若⼲的 as any 默默等待代码
review 时的公开处刑。
4. 项⽬急时间紧却发现 TypeScript 成了⾸要难题,思索⽚刻决定投靠的 Anyscript,快速开发业务逻辑,待到春暖花开时再回来补充类
型。双倍的⼯作量,双倍的快乐只有⾃⼰才懂。
为了避免以上悲剧的发⽣或者重演,我们只有在对它有更加深刻的理解之后,才能在开发时游刃有余、在撸码时纵横捭阖。TypeScript 类型系统简述
思考题
思考题:有⼈说 TypeScript = Type + Javascript,那么抛开 Javascript 不谈,这⾥的 Type 是⼀门完备的编程语⾔吗?TypeScript 的类型是⽀持定义 "函数定义" 的
有过编程经验的同学都知道,函数是⼀门编程语⾔中最基础的功能之⼀,函数是过程化、⾯向对象、函数式编程中程序封装的基本单元,其重要程度不⾔⽽喻。
函数可以帮助我们做很多事,⽐如 :
函数可以把程序封装成⼀个个功能,并形成函数内部的变量作⽤域,通过静态变量保存函数状态,通过返回值返回结果。
函数可以帮助我们实现过程的复⽤,如果⼀段逻辑可以被使⽤多次,就封装成函数,被其它过程多次调⽤。
函数也可以帮我们更好地组织代码结构,帮助我们更好地维护代码。
那么⾔归正传,如何在 TypeScript 类型系统中定义函数呢?
TypeScript 中类型系统中的的函数被称作 泛型操作符,其定义的简单的⽅式就是使⽤ type 关键字:
// 这⾥我们就定义了⼀个最简单的泛型操作符
type foo<T> = T;
这⾥的代码如何理解呢,其实这⾥我把代码转换成⼤家最熟悉的 Javascript 代码其实就不难理解了:
// 把上⾯的类型代码转换成 `JavaScript` 代码
function foo(T) {
return T
}
那么看到这⾥有同学⼼⾥要犯嘀咕了,⼼想你这不是忽悠我嘛?这不就是 TypeScript 中定义类型的⽅式嘛?这玩意⼉我可太熟了,这玩意⼉不就和 interface ⼀样的嘛,我还知道 Type 关键字和 interface 关键字有啥细微的区别呢!
嗯,同学你说的太对了,不过你不要着急,接着听我说,其实类型系统中的函数还⽀持对⼊参的约束。
// 这⾥我们就对⼊参 T 进⾏了类型约束
type foo<T extends string> = T;
那么把这⾥的代码转换成我们常见的 TypeScript 是什么样⼦的呢?
function foo(T: string) {
return T
}
当然啦我们也可以给它设置默认值:
// 这⾥我们就对⼊参 T 增加了默认值
type foo<T extends string = 'hello world'> = T;
那么这⾥的代码转换成我们常见的 TypeScript 就是这样的:
function foo(T: string = 'hello world') {
return T
}
那能不能像 JS ⾥的函数⼀样⽀持剩余参数呢?
看到这⾥肯定有同学迫不及待地想要提问了:那能不能像 JS ⾥的函数⼀样⽀持剩余参数呢?
很遗憾,⽬前暂时是不⽀持的,但是在我们⽇常开发中⼀定是有这样的需求存在的。那就真的没有办
法了嘛?其实也不⼀定,我们可以通过⼀些骚操作来模拟这种场景,当然这个是后话了,这⾥就不作拓展了。
TypeScript 的类型是⽀持 "条件判断" 的
⼈⽣总会⾯临很多选择,编程也是⼀样。
——我瞎编的
条件判断也是编程语⾔中最基础的功能之⼀,也是我们⽇常撸码过程成最常⽤的功能,⽆论是 if else 还是 三元运算符,相信⼤家都有使⽤过。
那么在 TypeScript 类型系统中的类型判断要怎么实现呢?
其实这在 TypeScript 官⽅⽂档被称为 条件类型(Conditional Types),定义的⽅法也⾮常简单,就是使⽤ extends 关键字。
T extends U ? X : Y;
这⾥相信聪明的你⼀眼就看出来了,这不就是 三元运算符 嘛!是的,⽽且这和三元运算符的也发也⾮常像,如果 T extends U 为 true 那么返回 X ,否则返回 Y。
结合之前刚刚讲过的 "函数",我们就可以简单的拓展⼀下:
type num = 1;
type str = 'hello world';
type IsNumber<N> = N extends number ? 'yes, is a number' : 'no, not a number';
type result1 = IsNumber<num>; // "yes, is a number"
type result2 = IsNumber<str>; // "no, not a number"
这⾥我们就实现了⼀个简单的带判断逻辑的函数。
TypeScript 的类型是⽀持 "数据结构" 的
模拟真实数组
jquery是什么有什么作用看到这⾥肯定有同学就笑了,这还不简单,就举例来说,TypeScript 中最常见数据类型就是 数组(Array) 或者 元组(tuple)。
作 push、pop、shift、unshift 这些⾏为操作吗?
元组类型 作
同学你说的很对,那你知道如何对
那你知道如何对 元组类型
其实这些操作都是可以被实现的:
// 这⾥定义⼀个⼯具类型,简化代码
type ReplaceValByOwnKey<T, S extends any> = { [P in keyof T]: S[P] };
// shift action
type ShiftAction<T extends any[]> = ((...args: T) => any) extends ((arg1: any, ...rest: infer R) => any) ? R : never;
// unshift action
type UnshiftAction<T extends any[], A> = ((args1: A, ...rest: T) => any) extends ((...args: infer R) => any) ? R : never;
// pop action
type PopAction<T extends any[]> = ReplaceValByOwnKey<ShiftAction<T>, T>;
// push action
type PushAction<T extends any[], E> = ReplaceValByOwnKey<UnshiftAction<T, any>, T & { [k: string]: E }>;
// test ...
type tuple = ['vue', 'react', 'angular'];
type resultWithShiftAction = ShiftAction<tuple>; // ["react", "angular"]
type resultWithUnshiftAction = UnshiftAction<tuple, 'jquery'>; // ["jquery", "vue", "react", "angular"]
type resultWithPopAction = PopAction<tuple>; // ["vue", "react"]
type resultWithPushAction = PushAction<tuple, 'jquery'>; // ["vue", "react", "angular", "jquery"]
注意:这⾥的代码仅⽤于测试,操作某些复杂类型可能会报错,需要做进⼀步兼容处理,这⾥简化了相关代码,请勿⽤于⽣产环境!
注意
相信读到这⾥,⼤部分同学应该可以已经可以感受到 TypeScript 类型系统的强⼤之处了,其实这⾥还是继续完善,为元组增加 concat
、map 等数组的常⽤的功能,这⾥不作详细探讨,留给同学们⾃⼰课后尝试吧。
但是其实上⾯提到的 "数据类型" 并不是我这⾥想讲解的 "数据类型",上述的数据类型本质上还是服务于代码逻辑的数据类型,其实并不是服务于 类型系统 本⾝的数据类型。
上⾯这句话的怎么理解呢?
类
批量操作,同理,服务于 类型系统 本⾝的数据结构,应该也可以对 类
数据 作 批量操作
不管是 数组 还是 元组,在⼴义的理解中,其实都是⽤来对 数据
批量操作。
型 作 批量操作
数组 是什么呢?
那么如何对 类型
批量操作 呢?或者说服务于 类型系统 中的 数组
类型 作 批量操作
下⾯就引出了本⼩节真正的 "数组":联合类型(Union Types)
说起 联合类型(Union Types) ,相信使⽤过 TypeScript 同学的⼀定对它⼜爱⼜恨:
1. 定义函数⼊参的时候,当同⼀个位置的参数允许传⼊多种参数类型,使⽤ 联合类型(Union Types) 会⾮常的⽅便,但想智能地推导出
返回值的类型地时候却⼜犯了难。
2. 当函数⼊参个数不确定地时候,⼜不愿意写出 (...args: any[]) => void 这种毫⽆卵⽤的参数类型定义。
3. 使⽤ 联合类型(Union Types) 时,虽然有 类型守卫(Type guard),但是某些场景下依然不够好⽤。
其实当你对它有⾜够的了解时,你就会发现 联合类型(Union Types) ⽐ 交叉类型(Intersection Types) 不知道⾼到哪⾥去了,~~我和它谈笑风⽣~~。
类型系统中的 "数组"
下⾯就让我们更加深⼊地了解⼀下联合类型(Union Types):
如何遍历联合类型(Union Types) 呢?
遍历,和⼤多数编程语⾔⽅法⼀样,在 TypeScript 类型系统中也是 in 关键字来遍历。
既然⽬标是 批量操作类型
批量操作类型,⾃然少不了类型的 遍历
type key = 'vue' | 'react';
type MappedType = { [k in key]: string } // { vue: string; react: string; }
你看,通过 in 关键字,我们可以很容易地遍历 联合类型(Union Types),并对类型作⼀些变换操作。
但有时候并不是所有所有 联合类型(Union Types) 都是我们显式地定义出来的。
我们想动态地推导出联合类型(Union Types) 类型有哪些⽅法呢?
可以使⽤ keyof 关键字动态地取出某个键值对类型的 key
interface Student {
name: string;
age: number;
}
type studentKey = keyof Student; // "name" | "age"
同样的我们也可以通过⼀些⽅法取出 元组类型 ⼦类型
type framework = ['vue', 'react', 'angular'];
type frameworkVal1 = framework[number]; // "vue" | "react" | "angular"
type frameworkVal2 = framework[any]; // "vue" | "react" | "angular"
实战应⽤
看到这⾥,有的同学可能要问了,你既然说 联合类型(Union Types) 可以批量操作类型,那我想把某⼀组类型批量映射成另⼀种类型,该
那我想把某⼀组类型批量映射成另⼀种类型,该怎么操作呢?
怎么操作呢
⽅法其实有很多,这⾥提供⼀种思路,抛砖引⽟⼀下,别的⽅法就留给同学们⾃⾏研究吧。
其实分析⼀下上⾯那个需求,不难看出,这个需求其实和数组的 map ⽅法有点相似
那么如何实现⼀个操作联合类型(Union Types) 的 map 函数呢?
// 这⾥的 placeholder 可以键⼊任何你所希望映射成为的类型
type UnionTypesMap<T> = T extends any ? 'placeholder' : never;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论