前言:这里的标题看起来是 "高级用法",不少同学可能就表示被劝退了。其实大纲Typescript
作为一门强类型
编程语言,最具特色的就是他的类型表达能力,这是很多完备的后端语言都难以媲美的 ~~说的很对,但PHP是最好的语言~~,所以如果你搞懂了他的类型系统,对将来的日常开发一定是大有裨益的,但过于灵活的类型系统也注定了Typescript
无法成为一门纯粹的静态语言,不过每一行代码都有代码提示他不香嘛?
- 基础准备
- Typescript 类型系统简述
- Typescript 的类型是支持定义 "函数定义" 的
- Typescript 的类型是支持 "条件判断" 的
- Typescript 的类型是支持 "数据结构" 的
- Typescript 的类型是支持 "作用域" 的
- Typescript 的类型是支持 "递归" 的
- "高级用法" 的使用场景与价值
- 哪些用法可以被称为 "高级用法"
- 举例说明 "高级用法" 的使用场景
- 类型推导与泛型操作符
- 流动的类型(类型编写思路)
- Typescript 代码哲学
- 常见类型推导实现逻辑梳理与实践入门
- 类型的传递(流动)
- 类型的过滤与分流
- Typescript 类型体操指北
- 定制化扩展你的 Typescript
- Typescript Service Plugins 的产生背景、功能定位、基础使用
- 市面上已有的 Typescript Service Plugins 举例介绍
- 参考资料链接
- Q&A(欢迎评论区补充)
- 可以利用 Typescript Service Plugin(例如配置 eslint 规则)阻塞编译或者在编译时告警吗?
此文档的内容默认要求读者已经具备以下知识:
- 有
Javascript
或其他语言编程经验。 - 有
Typescript
实际使用经验,最好在正经项目中完整地使用过。 - 了解
Typescript
基础语法以及常见关键字地作用。 - 对
Typescript
的类型系统
架构有一个最基本的了解。
- Typescript 官网
- Typescript Deep Dive
- Typescript GitHub地址
Typescript
开发的同学一定有这样的困扰:- 代码代码提示并不智能,似乎只能显式的定义类型,才能有代码提示,无法理解这样的编程语言居然有这么多人趋之若鹜。
- 各种各样的类型报错苦不堪言,本以为听信网上说
Typescript
可以提高代码可维护性,结果却发现徒增了不少开发负担。 - 显式地定义所有的类型似乎能应付大部分常见,但遇到有些复杂的情况却发现无能为力,只能含恨写下若干的
as any
默默等待代码review
时的公开处刑。 - 项目急时间紧却发现
Typescript
成了首要难题,思索片刻决定投靠的Anyscript
,快速开发业务逻辑,待到春暖花开时再回来补充类型。双倍的工作量,双倍的快乐只有自己才懂。
Typescript 类型系统简述
思考题:有人说Typescript 的类型是支持定义 "函数定义" 的有过编程经验的同学都知道,函数是一门编程语言中最基础的功能之一,函数是过程化、面向对象、函数式编程中程序封装的基本单元,其重要程度不言而喻。Typescript
=Type
+Javascript
,那么抛开Javascript
不谈,这里的Type
是一门完备的编程语言吗?
函数可以帮助我们做很多事,比如 :
- 函数可以把程序封装成一个个功能,并形成函数内部的变量作用域,通过静态变量保存函数状态,通过返回值返回结果。
- 函数可以帮助我们实现过程的复用,如果一段逻辑可以被使用多次,就封装成函数,被其它过程多次调用。
- 函数也可以帮我们更好地组织代码结构,帮助我们更好地维护代码。
Typescript
中类型系统中的的函数被称作 泛型操作符
,其定义的简单的方式就是使用 type
关键字:// 这里我们就定义了一个最简单的泛型操作符type foo = T;
这里的代码如何理解呢,其实这里我把代码转换成大家最熟悉的 Javascript
代码其实就不难理解了:// 把上面的类型代码转换成 `Javascript` 代码
function
foo
{
return
T
}
那么看到这里有同学心里要犯嘀咕了,心想你这不是忽悠我嘛?这不就是 Typescript
中定义类型的方式嘛?这玩意儿我可太熟了,这玩意儿不就和 interface
一样的嘛,我还知道 Type
关键字和 interface
关键字有啥细微的区别呢!嗯,同学你说的太对了,不过你不要着急,接着听我说,其实类型系统中的函数还支持对入参的约束。// 这里我们就对入参 T 进行了类型约束
type
foo
<
T
extends
string
>
=
T
;
那么把这里的代码转换成我们常见的 Typescript
是什么样子的呢?function foo {return T}
当然啦我们也可以给它设置默认值:// 这里我们就对入参 T 增加了默认值type foo = T;
那么这里的代码转换成我们常见的 Typescript
就是这样的:function foo {return T}
看到这里肯定有同学迫不及待地想要提问了:那能不能像 JS 里的函数一样支持剩余参数呢?很遗憾,目前暂时是不支持的,但是在我们日常开发中一定是有这样的需求存在的。那就真的没有办法了嘛?其实也不一定,我们可以通过一些骚操作来模拟这种场景,当然这个是后话了,这里就不作拓展了。Typescript 的类型是支持 "条件判断" 的人生总会面临很多选择,编程也是一样。条件判断也是编程语言中最基础的功能之一,也是我们日常撸码过程成最常用的功能,无论是——我瞎编的
if else
还是 三元运算符
,相信大家都有使用过。那么在 Typescript 类型系统中的类型判断要怎么实现呢?其实这在 Typescript
官方文档被称为 条件类型
,定义的方法也非常简单,就是使用 extends
关键字。T extends U X : Y;
这里相信聪明的你一眼就看出来了,这不就是 三元运算符
嘛!是的,而且这和三元运算符的也发也非常像,如果 T extends U
为 true
那么 返回 X
,否则返回 Y
。结合之前刚刚讲过的 "函数",我们就可以简单的拓展一下:type num = 1;type str = 'hello world';type IsNumber = N extends number 'yes, is a number' : 'no, not a number';type result1 = IsNumber; // "yes, is a number"type result2 = IsNumber; // "no, not a number"
这里我们就实现了一个简单的带判断逻辑的函数。Typescript 的类型是支持 "数据结构" 的模拟真实数组看到这里肯定有同学就笑了,这还不简单,就举例来说,Typescript
中最常见数据类型就是 数组(Array)
或者 元组(tuple)
。同学你说的很对,那你知道如何对 元组类型
作 push
、pop
、shift
、unshift
这些行为操作吗?其实这些操作都是可以被实现的:// 这里定义一个工具类型,简化代码type ReplacevalByOwnKey
注意:这里的代码仅用于测试,操作某些复杂类型可能会报错,需要做进一步兼容处理,这里简化了相关代码,请勿用于生产环境!相信读到这里,大部分同学应该可以已经可以感受到
Typescript
类型系统的强大之处了,其实这里还是继续完善,为元组增加 concat
、map
等数组的常用的功能,这里不作详细探讨,留给同学们自己课后尝试吧。但是其实上面提到的 "数据类型" 并不是我这里想讲解的 "数据类型",上述的数据类型本质上还是服务于代码逻辑的数据类型,其实并不是服务于 类型系统
本身的数据类型。上面这句话的怎么理解呢?不管是 数组
还是 元组
,在广义的理解中,其实都是用来对 数据 作 批量操作,同理,服务于 类型系统
本身的数据结构,应该也可以对 类型 作 批量操作。那么如何对 类型 作 批量操作 呢?或者说服务于 类型系统
中的 数组 是什么呢?下面就引出了本小节真正的 "数组":联合类型
说起 联合类型
,相信使用过 Typescript
同学的一定对它又爱又恨:
- 定义函数入参的时候,当同一个位置的参数允许传入多种参数类型,使用
联合类型
会非常的方便,但想智能地推导出返回值的类型地时候却又犯了难。 - 当函数入参个数不确定地时候,又不愿意写出
=> void
这种毫无卵用的参数类型定义。 - 使用
联合类型
时,虽然有类型守卫(Type guard)
,但是某些场景下依然不够好用。
联合类型
比 交叉类型
不知道高到哪里去了,~~我和它谈笑风生~~。类型系统中的 "数组"下面就让我们更加深入地了解一下 联合类型:如何遍历 联合类型 呢?既然目标是 批量操作类型,自然少不了类型的 遍历,和大多数编程语言方法一样,在 Typescript
类型系统中也是 in
关键字来遍历。type key = 'vue' | 'react';type MappedType = { [k in key]: string } // { vue: string; react: string; }
你看,通过 in
关键字,我们可以很容易地遍历 联合类型
,并对类型作一些变换操作。但有时候并不是所有所有 联合类型
都是我们显式地定义出来的。我们想动态地推导出 联合类型 类型有哪些方法呢?可以使用 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"
实战应用看到这里,有的同学可能要问了,你既然说 联合类型
可以批量操作类型,那我想把某一组类型批量映射成另一种类型,该怎么操作呢?方法其实有很多,这里提供一种思路,抛砖引玉一下,别的方法就留给同学们自行研究吧。其实分析一下上面那个需求,不难看出,这个需求其实和数组的 map
方法有点相似那么如何实现一个操作 联合类型 的 map 函数呢?// 这里的 placeholder 可以键入任何你所希望映射成为的类型type UnionTypesMap
其实这里聪明的同学已经看出来,我们只是利用了 条件类型
,使其的判断条件总是为 true
,那么它就总是会返回左边的类型,我们就可以拿到 泛型操作符
的入参并自定义我们的操作。让我们趁热打铁,再举个具体的栗子:把 联合类型 的每一项映射成某个函数的 返回值。
type UnionTypesMap2Func
相信有了上述内容的学习,我们已经对 联合类型
有了一个相对全面的了解,后续在此基础之上在作一些高级的拓展,也如砍瓜切菜一般简单了。其他数据类型当然除了数组,还存在其他的数据类型,例如可以用 type
或 interface
模拟 Javascript
中的 字面量对象,其特征之一就是可以使用 myType['propKey']
这样的方式取出子类型。这里抛砖引玉一下,有兴趣的同学可以自行研究。
Typescript 的类型是支持 "作用域" 的全局作用域就像常见的编程语言一样,在 Typescript
的类型系统中,也是支持 全局作用域 的。换句话说,你可以在没有 导入 的前提下,在 任意文件任意位置 直接获取到并且使用它。通常使用 declare
关键字来修饰,例如我们常见的 图片资源
的类型定义: declare module '*.png';declare module '*.svg';declare module '*.jpg';
当然我们也可以在 全局作用域 内声明一个类型:declare type str = string;declare interface Foo { propA: string; propB: number;}
需要注意的是,如何你的模块使用了 export
关键字导出了内容,上述的声明方式可能会失效,如果你依然想要将类型声明到全局,那么你就需要显式地声明到全局:declare global { const ModuleGlobalFoo: string;}
模块作用域就像 nodejs
中的模块一样,每个文件都是一个模块,每个模块都是独立的模块作用域。这里模块作用域触发的条件之一就是使用 export
关键字导出内容。
每一个模块中定义的内容是无法直接在其他模块中直接获取到的,如果有需要的话,可以使用 import
关键字按需导入。泛型操作符作用域&函数作用域泛型操作符是存在作用域的,还记得这一章的第一节为了方便大家理解,我把泛型操作符类比为函数吗?既然可以类比为函数,那么函数所具备的性质,泛型操作符自然也可以具备,所以存在泛型操作符作用域自然也就很好理解了。这里定义的两个同名的 T
并不会相互影响:type TypeOperator
上述是关于泛型操作符作用域的描述,下面我们聊一聊真正的函数作用域:类型也可以支持闭包:function Foo
Typescript 的类型是支持 "递归" 的Typescript
中的类型也是可以支持递归的,递归相关的问题比较抽象,这里还是举例来讲解,同时为了方便大家的理解,我也会像第一节一样,把类型递归的逻辑用 Javascript
语法描述一遍。
首先来让我们举个栗子:假如现在需要把一个任意长度的元组类型中的子类型依次取出,并用 `&` 拼接并返回。这里解决的方法其实非常非常多,解决的思路也非常非常多,由于这一小节讲的是 递归,所以我们使用。
免责声明:本平台仅供信息发布交流之途,请谨慎判断信息真伪。如遇虚假诈骗信息,请立即举报
举报