【前端从入门到入土系列】TypeScript基础语法
什么是 TypeScript
随着应用的庞大,项目中 JavaScript 的代码也会越来越臃肿,这时候许多 JavaScript 的语言弊端就会愈发明显,而 TypeScript 的出现,就是着力于解决 JavaScript 语言天生的弱势:静态类型。
TypeScript 中文网:https://tslang.cn/
TypeScript 视频教程:《TypeScript 精通指南》
为什么选择 TypeScript
几个月前,团队里觉得很多项目跑的越来越臃肿,团队协作遇到了一个很大的难题:如何让一个别人提供的方法产出一个一目了然的文档?因为一个项目总会涉及到多人协作:同学 A 编写了函数 a(),而 同学 B 在调用函数 a() 的时候得一直撸着 API 文档才能知道 a() 需要什么参数,会返回什么参数。
而 同学 A 后续又改动了函数 a(),但是却忘记了更新文档,这时候新接手项目的 同学 C 看着 API 文档和函数 a() 一脸懵逼,问题浮出水面:团队协作中,提供的接口如何描述自身?
这其中涉及到的问题有:
- 接口如何描述自己的参数和返回值?
- 接口参数和返回值在无数需求迭代中改变了多次,而这个 API 对应的文档该如何更新?
- 数据格式如何描述
我们意识到 JavaScript 在逐渐复杂的 Web 应用中缺少一个很重要的东西:静态类型。
因为需要有静态类型我们才可以知道这个函数需要的参数是什么,有什么类型,返回值是什么类型,哪些字段是可能空,哪些字段又需要什么样的格式。
我们首先找到了业界融合于 JavaScript 的方案:Flow
后来对比之后发现:
- 生态圈,TypeScript 的生态圈明显在 Flow 之上,各大主流类库都提供了 TypeScript 的类型声明文件,配合 visual studio code 编码体验上比 Flow 强太多。
- Flow 是侵入 JavaScript 的,对 JavaScript 做了一层增强;而 TypeScript 则为 JavaScript 超集,在 JavaScript 之上进行的语言抽象,最终编译成 JavaScript。
其中第二点很重要,因为团队成员对添加了 Flow 的 JavaScript 语法想当排斥,而我个人同样觉得 Flow 侵入 JavaScript 太过强烈,不如 TypeScript 直接做语言超集好,虽然二者出发点上没有太大区别,但从设计思想上来说 TypeScript 更吸引人。
TypeScript 的定位是做静态类型语言,而 Flow 的定位是类型检查器。
毕竟写着 Flow 的时候心里想的是我在写 JavaScript,而写 TypeScript 心里想的是我在写 TypeScript 🤣。
什么是 TypeScript
TypeScript 简称 TS。TypeScript 是 JavaScript 的超集,就是在 JavaScript 上做了一层封装,封装出 TypeScript 的特性,当然最终代码可以编译为 JavaScript。
TypeScript 早期的目标是为了让习惯编写强类型语言的后端程序员,能够快速的编写出前端应用(微软大法好),因为 JavaScript 没有强数据类型,所以 TypeScript 提供了静态数据类型,这是 TypeScript 的核心。
随着项目工程越来越大,越来越多的前端意识到静态数据类型的重要性,随着 TypeScript 的逐渐完善,支持者越来越多,静态数据类型的需求越来越强。于此同时, angular 2.x
这个领头羊率先使用 AtScript
开辟了静态数据类型战场。
JavaScript 行至今日,灵活,动态让它活跃在编程语言界一线。而灵活,动态使得它又十分神秘,只有运行才能得到答案。类型的补充填充了 JavaScript 的缺点,从 TypeScript 编译到 JavaScript,多了静态类型检查,而又保留了 JavaScript 的灵活动态。
简单来说:动态代码一时爽,重构全家火葬场。
静态类型
TypeScript 凭借 Microsoft 深厚的语言设计功底,设计的十分优雅和简单易用,学习成本非常低。
上面我们所说了,TypeScript 的核心就是静态数据类型,我们来简单了解一下静态数据类型和简单的类型推导,TypeScript 是以 *.ts
作为文件后缀的,我们创建一个 demo.ts
文件,写下这段代码:
1 | let num: number |
从上面的代码中,我们可以知道变量 num
是 number
类型的,如果我们给 num
赋其他类型的值,则会报错:
是不是很简单?是的,这就是 TypeScript 的核心。
我们再来看看一个函数该如何表达:
1 | const fetch = function (url: string): Promise { } |
fetch()
函数接收一个 string
类型的参数 url
,返回一个 Promise
。
以下是一个 JavaScript 的函数,不看方法内的写法我们完全不知道这个 API 会有哪些坑。
1 | export const fetch = function (url, params, user) { |
这是 TypeScript 的写法:
1 | export const fetch = function (url: string | object, params?: any, user?: User): Promise<object | Error> { |
这个 TypeScript 包含了很多信息:
url
可能是string
或object
类型params
是可以不传的,也可以传递任何类型user
要求是User
类型的,当然也是可以不传- 返回了一个
Promise
,Promise
的求值结果可能是object
,也有可能是Error
看到上面的信息后,我们大概知道可以这么调用并处理 fetch
的返回结果:
1 | let result = await fetch('https://tasaid.com', { id: 1 }) |
是不是很有意思?鹅妹子嘤!TypeScript 在说话,TypeScript 在让代码描述自身。
这就是静态数据类型的意义。静态类型在越复杂的应用中,需求越强烈。
这是 react
对于数据类型的约束:
1 | import PropTypes from 'prop-types' |
这是 vue
对于数据类型的约束:
1 | Vue.component('component', { |
而引入了 TypeScript 之后,就会感受到真正流畅的数据类型约束:
1 | class Component { |
和 ECMAScript 相比
TypeScript 是 JavaScript 的超集,目标也是对齐 ECMAScript 的标准规范和提案对齐,最终 TypeScript 也是编译为 JavaScript。
同时,和 JavaScript 规范标准 ECMAScript 提案 相比,TypeScript 也一直在跟进 ECMAScript 的许多新特性。
例如当前来说比较深受大家喜爱的新特性:
- import() 动态表达式
- decorators 装饰器
- async/await 异步
而这些都可以编译到 ECMAScript 3
(少数细节存在兼容性问题)。
小 tips
- 使用 visual studio code 编辑器会体验到 TypeScript 强大的类型推导,毕竟两个都是微软亲儿子
- 一些 JavaScript 编写的大型的第三方库,都提供了 TypeScript 的类型声明文件(
*.d.ts
文件), 一般都放在包目录的types
文件夹中。或者在@types/*
仓库名下可以找到 babel
是将高级版本的 JavaScript 编译为目标版本的 JavaScript,TypeScript
是将 TypeScript 编译为目标版本的 JavaScript。它们的编译是重叠的,也就是说TypeScript
可以不再依赖babel
编译。
基础特性和类型推导
基础类型
我们先简单的声明一些变量:
1 | let a: number |
当我们给这些变量赋错误的类型值的时候,会抛出类型错误异常。
是不是很简单,TypeScript 优秀的设计使得即使你没有接触过它,但是仍然能够读懂它。
复杂类型
1 | // array |
高级类型
1 | // 联合类型, foo 是 string 或 number |
Model
从前几年热门的 MVC
一直到现在热门的 MVVM
,我们发现无论是 MVC(Model-View-Controller) 还是 MVVM(Model-View-ViewModel),我们始终抛不开一个关键的地方 —— 数据层:Model
。
因为本质上整个页面的操作都是在进行数据流动,页面展现本质上都是数据,而我们通过 Model
来描述数据。
这是一个简单的 Model
演示:
1 | let user : { id: number, name: string } = { id: 1, name: 'linkFly' } |
在 TypeScript (或者是所有强 OO 语言)中,推荐以 Model
来描述数据的方式也就是 Class
。
这一小节只简单介绍 Class 和 泛型,实际项目中可能还会牵扯更多更强大的 OO 概念:接口、抽象类、继承类、继承属性。
这些知识不是一蹴而就的,而是需要在项目中不断探索不断组合的。
Class 类
所有类型的根本都是类,TS 中声明一个类的语法非常简单,可读性很高。
注意,TS 中类型是核心,当你想把一个项目从 JavaScript 迁移到 TypeScript 的时候,需要为项目中补充大量的类型,而这些类型大部分都是基于 Class
构建的。
这是一个简单的类:
1 | class User { |
当然随着需求的不同,也可以补充很多细节:
1 | class User { |
泛型
泛型是用来解决类型重用的问题。
例如下面一个函数,只能传递 number
的参数并返回:
1 | function identity(arg: number): number { |
现在想传递一个 string
类型的参数,然后也返回它,这个时候就可以使用泛型,使用泛型可以接收任意类型并返回:
1 | // 这个 T 就是泛型,也可以叫其他名字 |
我们可以轻松的使用泛型来实现数据包装:
1 | function fetch<T>(url: string): Promise<T> { |
小 tips
- TypeScript 中文网 可以看到完整 TS 类型。
- 在项目初期使用 TS 中,会需要很大的时间和精力,去编写和架构基本业务类型(Models),在此之后会越来越方便快捷。
- 一些没有行为只需要做类型检查的类型(没有方法的 Models),可以使用 TypeScript 声明文件 (*.d.ts),例如:
1 | declare namespace Models { |
这个系列的文章不会讲解 TypeScript 的声明文件,但是它是 TypeScript 中不可缺少的一部分。
- 尽量减少使用
any
类型,它意味着类型不可控 - 某些变量或者第三方库中属性无法感知,使用
as
强制进行类型推导即可。
1 | window.tempName = 'linkFly' |
引入和编译
快速使用
安装:
1 | npm i -g typescript |
编译:
1 | tsc helloworld.ts |
如果我们想快速测试一个文件,可以使用 ts-node,ts-node
可以让我们通过命令直接执行 *.ts
文件:
1 | npm i -g ts-node |
关于 tsc
,一般来说,全局安装的方案并不是很棒,我们可以把 typescript 安装到本地项目目录中:
1 | npm i --save-dev typescript |
这样在 package.json
的 scripts
中可以引用 tsc
命令:
1 | { |
使用 npm run build
命令即可启用 tsc
命令编译本地目录,typescript
会去查找目录下的 tsconfig.json
配置文件。
引入 TypeScript 非常简单,TypeScript 的文件后缀为 ts
,迁移 TypeScript 只需要将项目中,业务代码的 *.js
修改为 *.ts
即可。不过在此之后你会看到大量的报错,然后就是按照 TypeScript 的规则,解决这些报错即可:)
tsconfig.json
tsconfig.json
是 TypeScript 的编译选项文件,通过配置它来定制 TypeScript 的编译细节。
- 直接调用
tsc
,编译器会从当前目录开始去查找tsconfig.json
文件,逐级向上搜索父目录。 - 调用
tsc -p
,可以指定一个包含tsconfig.json
文件的目录进行编译。如果没有找到tsconfig.json
文件,TypeScript
会编译每个文件并在对应文件的同级目录产出。
如果你要编译的是一个 Node 项目,请先安装 Node 编译依赖:
npm i @types/node --save-dev
,否则会出现 Node 内置模块无法找到的情况。
一个 tsconfig.json
文件描述:
1 | { |
完整 tsconfig
配置选项的可以参考 这里,或者 tsconfig 的 json-schema。
注意: TypeScript 不会做 Polyfill
,例如从 es6
编译到 es5
,TypeScript 编译后不会处理 es6
的那些新增的对象的方法,如果需要 polyfill
需要自行处理!
完整的编译选项请参阅 TypeScript 中文网 和 TypeScript 官网。
编译
大多数前端已经使用各种各样的构建工具,完整构建工具集成列表参见 这里
对于 Node 项目,建议搭配 gulp
使用。不过个人更喜欢通过 npm scripts
脚本组合命令,然后直接使用 tsc
编译,例如我自己的编译方案。
项目目录为:
1 | |---output # 编译输出 |
package.json
中 scripts
脚本如下:
1 | "scripts": { |
- 使用
nodemon
监听整个server
目录文件改动并执行脚本 npm run clean
用于清空编译输出目录npm run build:ts
用于编译server
目录下的 TypeScript 文件npm run server
用于启动编译后的 node 服务器。
平常开发只需要 npm run dev
,生产使用 npm run build
产出文件即可。
visual studio code 集成和 debug
编译任务
visual studio code
编译 TypeScript
非常简单,根据上面我自己的组合命令,只需要在 vs code
的任务中加入编译脚本即可(npm run build-ts
):
./.vscode/task.json
如下:
1 | { |
按 control+shift+B
即可编译。
debug
在 ./.vscode/launch.json
中加入如下代码即可调试,记得要在 tsconfig.json
里打开 sourceMap
选项:
1 | { |
然后在 vs code
中给代码打上断点,按 F5,一步步调试即可。
结语
我们简单总结一下:
- 使用
ts-node
可以让我们直接运行*.ts
文件(不过只建议临时运行代码或特殊应用场景使用) - 使用
typescript
产出的tsc
命令来编译*.ts
文件到*.js
,然后我们运行编译出来的*.js
文件即可 - 使用
tsconfig.json
配置 TypeScript 的编译选项
装饰器和反射
前言
在了解装饰器之前,我们先看一段代码:
1 | class User { |
这段代码声明了一个 Class 为 User
,User
提供了一个实例方法 changeName()
用来修改字段 name
的值。
现在我们要在修改 name
之前,先对 newName
做校验,判断如果 newName
的值为空字符串,就抛出异常。
按照我们过去的做法,我们会修改 changeName()
函数,或者提供一个 validaName()
方法:
1 | class User { |
可以看到,我们新编写的 validateName()
,侵入到了 changeName()
的逻辑中。如此带来一个弊端:
- 我们不知道
changeName()
里面可能还包含了什么样的隐性逻辑 changeName()
被扩展后逻辑不清晰
然后我们把调用时机从 changeName()
中抽出来,先调用 validateName()
,再调用 changeName()
:
1 | let user = new User('linkFly', 1) |
但是上面的问题 1 仍然没有被解决,调用方代码变的十分啰嗦。那么有没有更好的方式来表现这层逻辑呢?
装饰器就用来解决这个问题:”无侵入式” 的增强。
装饰器
顾名思义,”装饰器” (也叫 “注解”)就是对一个 类/方法/属性/参数 的装饰。它是对这一系列代码的增强,并且通过自身描述了被装饰的代码可能存在的行为改变。
简单来说,装饰器就是对代码的描述。
由于装饰器是实验性特性,所以要在 tsconfig.json
里启用这个实验性特性:
1 | { |
钢铁侠托尼·史塔克只是一个有血有肉的人,而他的盔甲让他成为了钢铁侠,盔甲就是对托尼·史塔克的装饰(增强)。
我们使用装饰器修改一下上面的例子:
1 | // 声明一个装饰器,第三个参数是 "成员的属性描述符",如果代码输出目标版本(target)小于 ES5 返回值会被忽略。 |
这里我们可以看到,changeName
的逻辑没有任何改变,但其实它的行为已经通过装饰器 @validate
增强。
这就是装饰器的作用。装饰器可以用很直观的方式来描述代码:
1 | class User { |
装饰器工厂
装饰器的执行时机如下:
1 | // 这是一个装饰器工厂,在外面使用 @god() 的时候就会调用这个工厂 |
以上代码输出结果
1 | god(): evaluated test |
我们也可以直接声明一个装饰器来使用(要注意和装饰器工厂的区别):
1 | function god(target, propertyKey: string, descriptor: PropertyDescriptor) { |
装饰器全家族
装饰器家族有 4 种装饰形式,注意,装饰器能装饰在类、方法、属性和参数上,但不能只装饰在函数上!
类装饰器
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
1 | function sealed(constructor: Function) { |
方法装饰器
方法装饰器表达式会在运行时当作函数被调用,传入下列 3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 成员的名字
- 成员的属性描述符
{value: any, writable: boolean, enumerable: boolean, configurable: boolean}
1 | function god(name: string) { |
访问器装饰器
和函数装饰器一样,只不过是装饰于访问器上的。
1 | function god(name: string) { |
属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列 2个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 成员的名字
1 | function god(target, propertyKey: string) { |
参数装饰器
参数装饰器表达式会在运行时当作函数被调用,传入下列 3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 成员的名字
- 参数在函数参数列表中的索引
1 | const required = function (target, propertyKey: string, parameterIndex: number) { |
例如上面 validate
的例子可以用在参数装饰器上
1 | // 定义一个私有 key |
元数据反射
反射,就是在运行时动态获取一个对象的一切信息:方法/属性等等,特点在于动态类型反推导。在 TypeScript 中,反射的原理是通过设计阶段对对象注入元数据信息,在运行阶段读取注入的元数据,从而得到对象信息。
反射可以获取对象的:
- 对象的类型
- 成员/静态属性的信息(类型)
- 方法的参数类型、返回类型
1 | class User { |
例如上面的例子,在 TypeScript 中可以获取到这些信息:
- Class Name 为
User
User
有一个属性名为name
,有一个方法say()
- 属性
name
是string
类型的,且值为linkFly
- 方法
say()
接受一个string
类型的参数,在 TypeScript 中,参数名是获取不到的 - 方法
say()
返回类型为string
TypeScript 结合自身静态类型语言的特点,为使用了装饰器的代码声明注入了 3 组元数据:
design:type
: 成员类型design:paramtypes
: 成员所有参数类型design:returntype
: 成员返回类型
由于元数据反射也是实验性 API,所以要在 tsconfig.json
里启用这个实验性特性:
1 | { |
然后安装 reflect-metadata
1 | npm i reflect-metadata --save |
这样在装饰器中,就可以访问到由 TypeScript 注入的基本信息元数据:
1 | import 'reflect-metadata' |
结语
Java 和 C# 由于是强类型编译型语言,所以反射就成了它们动态反推导数据类型的一个重要特性。
目前来说,JavaScript 因为其动态性,所以本身就包含了一些反射的特点:
- 遍历对象内所有属性
- 判断数据类型
TypeScript 补充了基础的类型元数据,只不过还是有些地方不够完善:在 TypeScript 中,参数名通过反射是获取不到的。
为什么获取不到呢?因为 JavaScript 本质上还是解释型语言,还迎合 Web 有一大特色:编译和压缩…
- 编译完了之后 Class Name 可能叫做
User_1
- 压缩完了之后参数
myName
可能叫m
- 运行时可能传了 2 个,3 个,或者 N 个参数
angular 1.x
中使用的依赖注入,采用传字符串那么蹩脚的方式,也是对 JavaScript 反射机制的不完善做出的一种妥协。
路由进化
express 路由
首先我们来看一个简单的 express
路由 (router):
1 | // 对网站首页的访问返回 "Hello World!" |
在上面的路由代码我们演示了一个普通流水线式的路由。
基于上一篇文章中我们学到的装饰器和反射的知识,我们将要实现 路由的配置通过装饰器实现,并且实现一层路由逻辑的封装。
路由进化
基于装饰器和反射,我们要实现的路由最终效果是这样的:
1 | class Home { |
这段代码相比传统的路由配置,优点如下:
- 将路由的配置抽离成为了装饰器,让整个
router
函数内部只需要处理业务逻辑即可,路由配置简单明了 - 隐藏
req
和res
,每个router
直接返回结果即可,无需自己再输出结果
装饰器: HTTP Method
我们先编写 HTTP Method
的装饰器,我们将实现两个装饰器,分别叫做 httpGet
和 httpPost
,对应 HTTP Method 的 GET/POST
。
原理上,我们会将 router
配置的数据都挂到使用装饰器的方法上。
1 | import 'reflect-metadata' |
装饰器: path
有了上面 HTTP Method
装饰器的实现,我们再实现 path
装饰器将会很简单。
当然,我们还可以在 path
中实现对原方法的封装:隐藏 req
和 res
,并对 router 的输出结果进行封装。
注意这里使用的是装饰器工厂:
1 | import 'reflect-metadata' |
Router? Controller!
现在,我们需要将所有的 Router 按照自己的业务规则/或者自定义的其他规则进行归类 —— 然后提取出对应的 Class
,例如下面的 User Class
就是把用户信息所有的 router
都归类在一起:
1 | class User { |
然后在 express
配置的入口逻辑那里,把 class
对应的方法遍历一遍,然后使用 reflect-metadata
反射对应的 router
配置即可:
1 | import 'reflect-metadata' |
至此,我们的 express
路由进化完毕,效果如下:
完整的例子可以参考我的 Github。
结语
装饰器目前在 ECMAScript 新提案中的 建议征集的第二阶段(Stage 2),由于装饰器在其他语言中早已实现,例如 Java 的注解(Annotation) 和 C# 的特性(Attribute),所以纳入 ECMAScript 规范只是时间问题了。
装饰器来装饰路由,并且封装 router
操作的的思路缘起 .NET MVC
架构:
angular 2.x
使用也引入了装饰器作为核心开发,随着规范的推进,相信装饰器进入大家视野,应用的场景也会越来越多。
Vue 引入 TypeScript
Vue 引入 TypeScript
Vue 在 官方文档中有一节简单的介绍了如何引入 TypeScript,可惜文档太过简单,真正投入生产还有许多的细节没有介绍。
我们对此进行了一系列探索,最后我们的风格是这样的:
1 | import { Component, Prop, Vue, Watch } from 'vue-property-decorator' |
大体来说,Vue 引入 TypeScript 可以用到这些生态库:
- vue-class-component:强化 Vue 组件,使用 TypeScript/装饰器 增强 Vue 组件
- vue-property-decorator:在
vue-class-component
上增强更多的结合 Vue 特性的装饰器 - vuex-class:在
vue-class-component
提供Vuex
的绑定 - vuex-ts-decorators:让
Vuex
和 TypeScript 结合
下面我们一步步来介绍 Vue 如何引入 TypeScript
webpack 和 tsconfig.json 配置
TypeScript 为 Webpack 提供了 ts-loader
:
1 | npm i ts-loader --save-dev |
webpack 配置如下:
1 | { |
ts-loader
会检索当前目录下的 tsconfig.json
文件,如果找不到会一层层往上找。
这里有一份参考的 tsconfig.json
配置:
1 | { |
由于 TypeScript 默认并不支持 *.vue
后缀的文件,所以在 vue 项目中引入的时候需要创建一个 vue-shims.d.ts
文件,放在项目项目对应使用目录下,例如 src/vue-shims.d.ts
1 | declare module "*.vue" { |
意思是告诉 TypeScript *.vue
后缀的文件可以交给 vue
模块来处理。
而在代码中导入 *.vue
文件的时候,需要写上 .vue
后缀。原因还是因为 TypeScript 默认只识别 *.ts
文件,不识别 *.vue
文件:
1 | import Component from 'components/component.vue' |
vue-class-component
vue-class-component 对 Vue 组件进行了一层封装,让 Vue 的组件语法在结合了 TypeScript 语法之后更加扁平化:
1 | <template> |
上面的代码和下面没有引入 vue-class-component
的语法一样:
1 | export default { |
vue-property-decorator
vue-property-decorator 是在 vue-class-component
上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器:
@Emit
@Inject
@Model
@Prop
@Provide
@Watch
@Component
(从vue-class-component
继承)
这里仅列举常用的 @Prop/@Watch/@Component
:
1 | import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator' |
相当于:
1 | export default { |
vuex-class
vuex-class 在 vue-class-component
上,提供了 Vuex
的绑定的装饰器语法。
我们编写一个简单的 Vuex store:
1 | import Vue from 'vue' |
使用 vuex-class
之后:
1 | import { Component, Vue } from 'vue-property-decorator' |
vuex-ts-decorators
由于使用 vue-class-component
,在 Vue 组件中我们已经感受到了装饰器的强大语法糖,于是我们还希望在 Vuex Store 中也能使用装饰器的语法: vuex-ts-decorators 就是干这个事情的。
vuex-ts-decorators
可以让你结合装饰器来编写 Vuex Store。
由于 vuex-ts-decorators
提供的包是未经编译的 *.ts
代码,如果你排除了 node_modules
的编译,则需要在 ts-loader
中单独加上对它的编译:
1 | { |
然后就可以愉快使用它来编写 Vuex Store 了。
1 | import { module, action, mutation } from 'mfe-vuex-ts-decorators' |
vuex-ts-decorators
文档没有提及 @module
装饰器的参数:
1 | @module({ |
注意事项
vuex-ts-decorators
这个项目目前最后一次更新时间是 2017 年 2 月 21 日,原作者说自己正在开发对应的新项目,这个项目已经不再维护,但是目前还没有看到新项目的进展。
但可以肯定的是,原作者已经不再维护 vuex-ts-decorators
这个项目。
我们的做法是在这个项目上重新 fork 一个新项目,并且修正了遇到的隐藏 bug。后续我们会自己维护这个项目。(fork 到了内部项目中,后续在这个项目基础上进行二次开发,到时候会公布出来,当然,其实它的功能很简单完全可以自己开发一个)
目前已知 bug:
当使用 @module({ store: false })
后,被包装的 class 会返回处理后的 Vuex Store,结构为: { state: any, getters: any, modules: any, actions: any, mutations: any }
。
如果最后 new Vuex.Store({ options: object })
的时候传递的不是 @module
包装后的 Vuex Store(例如对这个 Vuex Store 做了一层深拷贝),则会导致 mutations
中 this 丢失。
bug 点在 这一行,处理办法:
1 | store.mutations[name] = (state: any, ...args: any[]) => { |
原因是因为 vuex-ts-decorators
一直在自己内部引用着生成的 Vuex Store,如果最终 new Vuex.Store()
的时候没有传递它自己生成的 Vuex Store 就会导致引用不正确。
结语
围绕 vue-class-component
展开的生态圈,如同一把利剑,通过各种装饰器,能够结合 TypeScript 将 Vue 武装的更加强大。在这篇文章发布之际,很巧的是:Vue 作者尤雨溪也宣布在 Vue 2.5 以后将全面支持 TypeScript(译文看这里),并且声明Vue 将尽力做到和 vue-class-component 兼容。
前端的场景已经不再像当年一样简单的切图、画简单的样式和写简单的脚本。应用越来越庞大,需求越来越复杂。TypeScript 有一个很好的切入点:从语言的角度解决了大型 Web 应用的静态类型约束痛点。
相信随着时间的推移,会有越来越多的人和框架加入到 TypeScript 大军。
至此,我们的 《从 JavaScript 到 TypeScript》 系列文章已经结束。最后,Welcome to TypeScript!