在Vue 中使用Typescript

Vue 中使用 typescript

什么是typescript

typescript 为 javaScript的超集,这意味着它支持所有都JavaScript都语法。它很像JavaScript都强类型版本,除此之外,它还有一些扩展的语法,如interface/module等。
typescript 在编译期会去掉类型和特有语法,生成纯粹的JavaScript。

Typescript 5年内的热度随时间变化的趋势,整体呈现一个上升的趋势。也说明ts越来越️受大家的关注了。
google 趋势

安装typescript

npm install -g typescript
tsc greeter.ts

举个栗子

左右对比可以看出typescript 在编译期会去掉类型和特有语法,生成纯粹的JavaScript。
greeter.ts

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: "Jane", lastName: "User" };

greeter.js

function greeter(person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}
var user = { firstName: "Jane", lastName: "User" };

为什么需要使用它?

优势:

  1. 静态类型检查
  2. IDE 智能提示
  3. 代码重构
  4. 可读性

1. 静态类型检查

静态类型检查首要优点就是能尽早的发现逻辑错误,而不是上线之后才发现。
1.1 类型分析
传参过程字段错误,或类型错误使用。(进行参数标注后,在编码过程中即可检查出错误。)
1.2 类型推断:函数的返回值可通过ts类型推断得出.这一步骤是 在编译时进行
在编译时进行类型分析

example:
eg1: 我在使用ts写vue-router 的 动态路径参数时就发现了一个问题, 动态路径参数 以冒号开头 path: '/user/:id',我们会误认为id为一个number,如果使用ts你将得到提示 我们应该传入一个string类型的id. 传入一个number类型的id可能并不会出错,js会对它进行隐式类型转换,但是传入一个string会使它更安全和规范.

eg2: 个人使用后的效果

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person): string {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: 1223, lastname: "User" };

greeter(user);

greeter error

2.智能补全

在编写代码时ide就会提示函数签名.

interface Person {
    firstName: string;
    lastName: string;
}
/**
 * 问候语句
 * @param {Person} person
 * @returns {string}
 */
function greeter(person: Person): string {
    return "Hello, " + person.firstName + " " + person.lastName;
}

/**
 * hello word!
 *
 * @param {string} word
 * @returns {string}
 */
function Hello(word: string): string {
    return "hello," + word;
}

export { greeter, Hello };

直接将这个ts文件引入到其他ts文件中,不仅补全了所有的参数类型,还告诉你需要填入一个参数,并且你只有填入一个Person类型的对象才不会报错。(智能补全和参数校验)
greeter 函数

3.在重构上

动态一时爽,重构火葬场.
typescript 在重构上的优势,我们主要从三方面说明。

  1. 重命名符号,可将一切引用的地方都进行修改。
    在vs code 中如果我们想修改函数、变量或者类的名称,我们可以使用重命名符号的功能,在当前项目中正确的修改所有的引用.这个既可以在ts中使用,也可以在js中使用,而它的底层实现都是依靠ts 的语法分析器实现的。
  2. 自动更新引用路径(vs code)。
    在重构的过程中,我们可能需要移动文件的路径,这往往会导致其他地方的import失效,这时候vs code提供了自动更新引用路径的功能。它的底层实现也是依靠ts 的语法分析器实现的。
  3. 校验函数签名。
    有时候我们会重构类或函数的签名,如果有引用到的地方忘记修改,除了运行时候能发现,其他时候往往难以察觉,且 ESLint 也只能是排查简单的问题,所以出了BUG会非常麻烦。 而 TypeScript 不一样,在编码时就能及时的发现哪里错了,哪里应该改动但没有修改。

[函数签名 MDN][5]

4. 可读性

可读性上,TypeScript 明显占优,查看开源代码时,如果注释不是很完善,往往会看的云里雾里,而 TypeScript 在同等条件下,至少有个类型,能让自己更容易明白代码的参数、返回值和意图。

TS+Vue初探

配置

在正式开发之前,我们需要了解一些基本的配置。
1.tsconfig.json 是 ts 项目的编译选项配置文件. 在 ts 项目中如果你不添加这份文件,ts 会使用默认的配置. 扫描二维码获取配置项目。
tsconfig.json 配置
2. ts-loader:Webpack 的TypeScript 加载器,就是为了让 webpack 编译 .ts .tsx文件。
3. TSLint:.ts .tsx文件的代码风格检查工具。(作用类似于ESLint)
4. vue-shim.d.ts:由于 TypeScript 默认并不支持 *.vue 后缀的文件,所以在 vue 项目中引入的时候需要创建一个 vue-shim.d.ts 文件,放在项目根目录下,例如 src/vue-shim.d.ts。
vue-shim.ts

在Vue里面写TS的方式

图中内容应写在script中的lang="ts" 。
上图:使用 Vue.extend的基础用法。
vue.extend
下图:基于类的Vue组件
vue 组件
通过对比我们发现,上面的编码方式更接*我们*时JS的写法,但是我们并不能感受到ts的类型检查和类型推断。
下面张图的写法更有助于我们得到ts的类型检查。这需要我们引入 vue-class-component ,虽然template中还是不能得到补全,但是script 中的内容得到了更好的补全。 下面我们了解一下vue-class-component的作用。

vue-class-component & vue-property-decorator

vue-class-component 强化 Vue 组件,使用装饰器语法使 Vue 组件更好的跟TS结合使用。
vue-property-decorator在 vue-class-component 的基础上增加了更多与 Vue 相关的装饰器,使Vue组件更好的跟TS结合使用。

这两者都是离不开装饰器的,(decorator)装饰器已在ES提案中。Decorator是装饰器模式的实践。装饰器模式呢,它是继承关系的一个替代方案。动态地给对象添加额外的职责。在不改变接口的前提下,增强类的性能。下面我们以 钢铁侠 为例讲解如何使用 ES7 的 decorator。

以钢铁侠为例,钢铁侠本质是一个人,只是“装饰”了很多武器方才变得那么 NB,不过再怎么装饰他还是一个人,它本质是没有被改变的。所以,装饰器没有改变其继承关系,但也同样能够为它添加很多厉害的技能。简单的说,装饰器是在不修改一个类的继承关系的前提下,为一个类修改或添加成员。
superman
装饰器主要接收的三个参数:
target 要在其上定义属性的对象。
key 要定义或修改的属性的名称。
descriptor 将被定义或修改的属性描述符。
下面我们通过代码中我们为一个人添加了飞行的功能:
decoractor

typescript VS JavaScript

了解了上面的基础知识,现在我将同一段代码分别使用js 和 ts来书写,现在我们来对比他们之间的差别。
js vs ts

  1. Props (Properties)
    使用js,我们有很多中方式来定义组件的 Props,但是大多都掺杂了 Vue 的私有特征,与 ES 格格不入,例如左边的代码,明明我们是把这个对象的 prop 属性定义成为了一个包含两个 string 元素的对象,但是我们却可以直接通过这个对象来访问 "name" 字段,这很明显是不符合 ES 语义的。
    再来看看右边的 TS 选手,通过 Prop 装饰器把指定的字段标记为了 Prop,既保留了 ES 语法的语义,而且还能与 Vue 完美的配合,更棒的是,我们可以在编码的过程中享受 TS 对 Prop 字段的静态类型检查。
  2. Method 和 data
    再来看看 Method,JS 中定义 method 还是有我们上面提到的那个不符合 ES 语义的毛病。而在 TS 中,method 不需要额外的装饰器——实例方法就会自动成为 Vue 组件的 method。类似的还有 data ,使用 TS 的语法,实例字段即可自动成为 Vue 组件的 data。
  3. Computed
    在传统的使用 JS 编写的 Vue 代码中,如果要定义计算属性,我们需要在 computed 属性中定义相应的函数。而这在 ES 中其实早就已经有了对应语义的语法——getter,所以在使用了 vue-class-component 的 vue 组件中,我们可以直接使用 getter 来定义计算属性,不管是在语法上还是在语义上,相比普通的 JS 都略胜一筹

总结:我们使用vue-class-component让vue组件的定义更加符合ES语义,使得TS能够更好的进行语法分析,并基于此进行类型检查。

业务场景中使用TS + Vue

1. 在Vue项目中定义data和props

vue-property-decorator 这样的写法,让我产生了两个疑惑。
1.1 为什么使用 的写法
@Prop(Number!😃 propA!: number
而不是
@Prop(Number) propA: number
1.2 为什么Prop需要前后写两次类型?

自我自答环节:
答1.1:因为我们定一个Phone这个类,没有对phone、condition这些字段通过constructor进行初始化,所以需要在属性上使用 显式赋值断言来帮助类型系统识别类型,这样能够让属性会被间接地初始化。
答1.2:前面括号里面的类型标注是Vue提供的类型检查。冒号后面的是为TS提供的类型标注。

2. 编写一个函数

这里我们将使用到js的接口,它经常用于定义复杂的参数类型和返回值类型。
interface
上面的例子,我们需要为opts这个参数定义类型,方便后面编码时的使用,这时我们就需要编写一个接口来对它进行类型定义。这个接口里面包括三个必须属性和一个可选属性。必须属性在对象初始化的时候必须赋值,但是有时候某个对象中的属性可以被忽略的,不一定会被需要。我们可以将它设置为可选项。

随着业务的发展,一些参数的字段会变的越来越多,越来越复杂。可能你想有没有什么一劳永逸的方法,让接口更加简单,甚至让它自动的适应业务的变化。于是我想出了这样的代码:
type interface

上面的代码: 定义了一个键为string,他的值为number或string类型的接口,并且该接口的所有字段都是可选的,你甚至可以传入一个空对象。所以我们可以使用上面的接口可以代替下面的接口,但是反之不行.

然而我们不应该去绕开这些检查,因为这样ts就不会为你检查使用接口的对象应该存在那些属性或者方法了。使用ts的意义就被大大减弱了。

3. 编写第三方依赖

在日常的开发过程中,我们经常会遇到将第三方依赖引入到 TS 项目中没有类型检查的问题,这往往是因为这些项目没有提供类型定义文件。这个时候,我们可以手动为这些第三方库编写类型定义文件,类型定义文件在编译后会被完全忽略,所以并不会对现有代码产生影响。以上面这个较为复杂的函数为例,它的作用是将传入的所有的参数的所有字段合并到一个新的对象中并返回,尽管他的功能比较简单,但是为它编写类型定义还是需要一些 TS 的技巧。
assgin1. 外部模块声明: 首先我们需要创建一个拓展名为 .d.ts 的文件,并在其中声明一个模块,声明的模块名称需要跟我们在其他文件中引入的路径相同。
外部模块声明 2. 类型参数——泛型:首先让我们考虑最简单的情况,当传入一个参数的时候,extend 函数应该返回与参数类型相同的对象,但是我们在编写函数的时候并不知道用户会传入何种类型的参数,所以我们可以定义一个类型参数 T1,这时,extend 就被称为泛型函数,T1 也被称做泛型参数。在上面的例子中,extend 函数接受一个类型为 T1 参数并返回一个类型为 T1 的值,T1 需要用户手动传入,还好 TS 足够聪明,在绝大多数情况下,TS 可以根据参数类型来自动推断类型参数,免去了我们输入类型参数的繁琐步骤。只接受一个参数的 extend 函数并没有很复杂,我们可以继续考虑一些更复杂的情况。
繁星
这次让我们定义一个接受三个参数的 extend 函数,同时它也接受三个泛型参数,而它返回值类型呢,则是 T1, T2, T3 的交叉类型。交叉类型让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,相当于对这些类型的成员求并集。 例如, T1 & T2 & T3 这个类型的对象同时拥有了这三种类型的成员。在这里,交叉类型就满足了我们对 extend 函数返回值类型的要求。值得注意的是,实际的 extend 函数可以接受不定个数的参数,也就是说,我们为它编写的类型定义也需要同时兼容接受不定个数参数的情况,这就需要 TS 提供的函数重载功能。
泛型3. 重载:在大多数静态类型编程语言中,编译器允许存在参数类型、个数不同的多个同名函数,这个特性被称为函数重载。TS 支持函数重载的特性,所以我们可以定义多个接受不同数量参数的 extend 方法,在用户调用时,TS 会自动的在这些同名函数中选择正确的重载定义。有了函数重载的帮助,我们可以在使用 extend 的大多数场景下享受到类型检查的好处,只有在参数个数超过4个的时候,TS 才无法推断出返回值类型。需要注意的是在 JS 中,运行时并不提供函数重载的能力,我们无法定义多个同名函数,即使他们接受的参数数量并不相同,为了实现函数重载的效果,开发人员需要手动在单个函数中对参数的类型、数量做出判断。
重载
到这里我们的第三方声明就完成了,即使一个简单函数的第三方声明,我们也运用了很多ts的相关知识。

个人感受

前面我们讲解了,使用在vue中使用ts能带给我们的种种便利,现在就我个人感受而言,说一下美中不足的地方。
1.即使使用了ts,template 部分仍没有静态类型检查和IDE智能提示,但官方成员表示在以后的 Vue 单文件中会提供这项功能。
2.将  Vue 单文件组件引入 TS  文件中,无法正确的提示其文件位置。
3.Vue  周边工具,比如  Vuex,它对ts的支持薄弱,大量的功能难以直接迁移到ts中,并且没有好的官方支持的方案。
4.毫无疑问,使用 TS 进行开发,相比于 JS ,我们需要花费更多的时间和精力。

posted @ 2018-09-07 20:37  stone-lyl  阅读(23273)  评论(0编辑  收藏  举报