不能否认,现在 TypeScript 已成为一个前端工程师的所须要具有的基本技能。严谨的类型检测,一方面是提升了程序的可维护性和健壮性,另外一方面也在潜移默化地提升咱们的编程思惟,即逻辑性。javascript
那么,今天我将会经过结合实际开发场景和 Vue 3.0 源码中的部分类型定义来简单聊聊 TypeScript 中的高级类型。前端
interface
被用于对全部具备结构的数据进行类型检测。例如,
在实际开发当中,咱们会定义一些对象或数组来描述一些视图结构。vue
定义对象java
常见的有对表格的列的定义:ios
const columns = [ {key: "username", title: "用户名"}, {key: "age", title: "年龄"}, {key: "gender", title: "性别"} ]
而这个时候,咱们就能够经过定义一个名为 column
的接口:typescript
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
具有这些方法,它看起来会是这样: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 | string numberOrString = 1 numberOrString = '1'
而且,在实际的开发中使用联合类型,咱们一般会遇到这样的提示,例如:
interface student { name: string age: number } interface teacher { name: string age: number class: string } let person: student | tearcher = {} as any person.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" }
有了类型保护,这个时候咱们就会遇到这样的问题:若是个人联合类型中存在多个类型,那么岂不是得定义多个相似 isTeacher
这样的借助类型保护的函数?幸运的是,在 TypeScript 使用 typeof
或 instanceof
能够自动实现类型保护和区分。
在 TypeScript 中对基础类型使用 typeof
时,会自动进行类型保护,例如:
function isNumberOrString(param: string | number) { if (typeof param === 'string') { return param.split('') } else { return param++ } }
不一样于 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 = number const 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 编程能力会有质的飞跃。
写做不易,若是你以为有收获的话,能够帅气三连击!!!
参考资料:
)
往期文章回顾