前言
不可否认,现在 TypeScript 已成为一个前端工程师的所须要具备的基本技能。谨严的类型检测,一方面是进步了程序的可维护性和健壮性,另一方面也在耳濡目染地进步咱们的编程思维,即逻辑性。
那么,明天我将会通过联合理论开发场景和 Vue 3.0 源码中的局部类型定义来简略聊聊 TypeScript 中的高级类型。
interface
interface
被用于对所有具备构造的数据进行类型检测。例如,
在理论开发当中,咱们会定义一些对象或数组来形容一些视图构造。
定义对象
常见的有对表格的列的定义:
const columns = [ {key: "username", title: "用户名"}, {key: "age", title: "年龄"}, {key: "gender", title: "性别"}]
而这个时候,咱们就能够通过定义一个名为 column
的接口:
interface column { key: string, title: string}// 应用const columns: column[] = [ {key: "username", title: "用户名"}, {key: "age", title: "年龄"}, {key: "gender", title: "性别"}]
定义函数
咱们平时开发中应用的 axios
,它的调用形式会有很多种,例如 axios.request()
、axios.get()
、axios.put()
。它实质上是定义了这么一个接口来束缚 axios
具备这些办法,它看起来会是这样:
export interface Axios { request(config: AxiosRequestConfig): AxiosPromise get(url: string, config?: AxiosRequestConfig): AxiosPromise delete(url: string, config?: AxiosRequestConfig): AxiosPromise head(url: string, config?: AxiosRequestConfig): AxiosPromise options(url: string, config?: AxiosRequestConfig): AxiosPromise post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise}
继承
可复用性是咱们平时在写程序时须要常常思考的中央,例如组件的封装、工具函数的提取、函数设计模式的应用等等。而对于接口也同样如此,它能够通过继承来复用一些曾经定义好的 interface
或 class
。
这里咱们以 Vue 3.0 为例,它在 compiler
阶段,将 AST Element
节点 Node
分为了多种 Node
,例如 ForNode
、IFNode
、IfBranchNode
等等。而这些非凡的 Node
都是继承了 Node
:
Node
的 inteface
接口定义:
export interface Node { type: NodeTypes loc: SourceLocation}
IFNode
的 interface
接口定义:
export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] codegenNode?: IfConditionalExpression}
能够看到 Node
的接口定义是十分污浊的,它形容了 Node
所须要具备最根本的属性:type
节点类型、loc
节点在 template
中的起始地位信息。而 IFNode
则是在 Node
的根底上扩大了 branches
和 codegenNode
属性,以及重写了 Node
的 type
属性为 NodeTypes.IF
。
这里简略介绍一下 IFNode
的两个属性:
branches
示意它对应的else
或else if
的节点,它可能是一个或多个,所以它是一个数组。codegenNode
则是 Vue 3.0 的AST Element
的一大特点,它形容了该节点的一些属性,例如isBlock
、patchFlag
、dynamicProps
等等,这些会和runtime
的时候靶向更新和靶向更新密切相关。
近段时间,我也在写一篇对于 Vue 3.0 如何实现runtime
+compile
优雅地实现靶向更新和动态晋升的文章,应该会在下周末完工。
小结
对于 interface
的介绍和应用,咱们这里点到即止。当然,它还有很多高级的应用例如联合泛型、定义函数类型等等。有趣味的同学能够自行去理解这方面的实战。
穿插类型和联结类型
穿插类型
穿插类型故名思意,有着穿插之效。咱们能够通过穿插类型来实现多个类型的合并。例如,在 Vue3 中,compile
阶段除了会进行 baseParse
之外,还会进行 transform
,这样最初的 AST
才会进行 generate
生成可执行的代码。所以,compile
阶段它就会对应多个 options
,即它也是一个穿插类型:
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
而这个 CompilerOptions
类型别名会用于 baseCompiler
阶段:
export function baseCompile( template: string | RootNode, options: CompilerOptions = {}): CodegenResult {}
而为什么是多个options
的穿插,是因为baseCompiler
只是最根底的compiler
,下面还有更高级的compiler-dom
、compiler-ssr
等等。
联结类型
同样地,联结类型其有着合的成果。即有时候,你心愿一个变量的类型是 String
或者是 number
的时候,你就能够应用联结类型来束缚这个变量。例如:
let numberOrString: number | stringnumberOrString = 1numberOrString = '1'
并且,在理论的开发中应用联结类型,咱们通常会遇到这样的提醒,例如:
interface student { name: string age: number}interface teacher { name: string age: number class: string}let person: student | tearcher = {} as anyperson.class = "信息161"// property 'person' does not exit on type `student`
这个时候,咱们能够利用类型断言,通知 TypeScript 咱们晓得 person
是什么:
(person as teacher).class = "信息161"
小结
对于穿插类型和联结类型,应该是属于咱们平时开发中会应用频繁的局部。并且,从它们的概念上,不难理解,它们实质上就是数学中的并集和交加,只不过在根本类型上有着不同的体现而已。
类型爱护与辨别类型
类型爱护
在讲联结类型的时候,咱们说了它实质上是交加,这也导致了咱们不能间接应用交加之外的属性或办法。所以,咱们得通过在不同状况下应用类型断言来告知 TypeScript 它是什么,从而应用交加之外的属性。
然而,频繁地应用类型断言无疑升高了代码的可读性。而针对这一问题,TypeScript 提供了类型爱护的机制来缩小类型断言,它是一个主谓宾句,例如依然是下面的例子,咱们能够实现这样的类型爱护:
function isTeacher(person: Tearcher | Student): person is Teacher { return (<Tearcher>person).class !== undefined}
而后通过 isTeacher
这个函数来判断以后类型,再进行相应地属性拜访:
if (isTeacher(person)) { // 这里拜访 teacher 独有的属性 person.class = "信息162"} else { person.name = "wjc"}
typeof 与 instanceof
有了类型爱护,这个时候咱们就会遇到这样的问题:如果我的联结类型中存在多个类型,那么岂不是得定义多个相似 isTeacher
这样的借助类型爱护的函数?侥幸的是,在 TypeScript 应用 typeof
或 instanceof
能够主动实现类型爱护和辨别。
typeof
在 TypeScript 中对根底类型应用 typeof
时,会主动进行类型爱护,例如:
function isNumberOrString(param: string | number) { if (typeof param === 'string') { return param.split('') } else { return param++ }}
instanceof
不同于 typeof
只能对根底类型进行类型爱护,instanceof
能够实现对所有类型的类型爱护。它是通过构造函数来实现类型爱护。
interface Person { name: string age: string } class Teacher implements Person { name: string age: string constructor(name: string, age: string) { this.name = name this.age = age } teach() { console.log("i am a teacher.") } } class Student implements Person { name: string age: string constructor(name: string, age: string) { this.name = name this.age = age } study() { console.log("i am a student.") } } const person: Student | Teacher = null as any if (person instanceof Teacher) { person.teach() } else { person.study() }
小结
对于 typeof
或 instanceof
其实在 JavaScript 中也是陈词滥调的知识点,因为传统的类型检测咱们会更偏差应用 Object.String.prototype.toString()
来实现。然而,反而在 TypeScript 中,它们两者反而混的蛟龙得水。
类型别名
类型别名,我想大家脑海中第一工夫想到的就是 Webpack 中的 alias
为门路配置别名。然而,TypeScript 中的类型别名与它只是形同,然而意不同。艰深点讲,就是通过它能够给类型起一个名称:
type age = numberconst getAge = () => age// 等同于interface Age { getAge():number}
字面量类型
字面量类型是指咱们能够通过应用类型别名和联结类型来实现枚举类型,例如:
type method = get | GET | post | POST | PUT | put | DELETE | delete
索引类型与映射类型
对于索引类型和映射类型,这里咱们通过 loadash
中罕用的一个函数 pluck
来解说:
在 JavaScript 中实现:
function pluck(object, names) { return names.map(name => object[name])}
在 TypeScript 中实现:
function puck<T, K extends keyof T>(object: T, names: K[]): T[K][] { return names.map(name => object[name])}
这里咱们为 puck
函数定义了两个泛型变量 T
和 K
,其中 K
是继承于 T
中所有属性名的类型,所以形参中 names
被束缚为 T
中属性的数组,这个过程被称为类型索引。而对于 puck
函数的返回值 T[K][]
,则代表返回的值是一个数组,并且数组值被束缚为 T
中属性值为 K
的值,这个过程被称为索引拜访。
了解这两种概念可能会有点艰涩,然而对于每一者都离开去了解过程会比拟有逻辑性。
写在最初
尽管,TypeScript 已成为一项前端工程师的必备技能。然而,置信很多小伙伴还是用的 Javascript 比拟多。所以,可能会存在困扰,我该如何进步 TypeScript 编程能力?其实,这个问题很简略,开源的时代,明天咱们很多问题都能够通过浏览一些开源的我的项目源码来解决。这里,我举荐大家能够尝试着去浏览 Vue3.0 的源码,置信通过浏览,你的 TypeScript 编程能力会有质的飞跃。
写作不易,如果你感觉有播种的话,能够帅气三连击!!!
参考资料:
- 《TypeScirpt 实战指南》
- TypeScript 官网文档
- vuejs/vue-next
)
往期文章回顾
- 从零到一,带你彻底搞懂 vite 中的 HMR 原理(源码剖析)