搭建vue-cli时的配置:
TypeScript在Vue中的应用 - 图1

打开项目,发现最大的不同是额外引入了一个库 vue-property-decorator 以及多了三个文件: shims-tsx.d.tsshims-vue.d.tstsconfig.json

  • shims-tsx.d.ts 允许你使用 .tsx 结尾的文件,用来在 Vue 项目中使用 tsx 语法。 tsx 语法就是 jsx 语法的 typescript 版本。
  • shims-vue.d.ts 默认情况下, typescript 不支持导入 .vue 文件,这个文件让 typescript 识别出 .vue 文件并让其按照 VueConstructor<Vue> 的形式来处理。
  • tsconfig.json 保存的是 typescript 编译器的配置,可以按照需求修改来达到想要的效果。
  • 由于 vue-property-decorator 的存在,我们可以使用基于类的注释装饰器进行开发。

vue 提供了两种使用 typescript 编写组件的方法,一个是使用 Vue.componentVue.extend 定义组件。这种方式基本无学习成本,直接在现有的写法上结合 typescript 语法即可。但是这种方法不能完全实现 mixins 多混入的效果,只能混入一个。不推荐用这种方式写,无法实现多继承。
还有一种方式是 基于类的 Vue 组件,学习成本较高,需要对类有一定的了解。主要用到 vue-property-decorator ,下面重点介绍这种方式:

vue-property-decorator 主要提供了多个装饰器和一个函数:

data

对于 data 里的变量定义,我们可以直接按 typescript 定义类变量的写法写就可以。
例子:

  1. <script lang="ts">
  2. import { Vue, Component } from 'vue-property-decorator';
  3. @Component({})
  4. export default class "组件名" extends Vue {
  5. name: string = "huangry";
  6. age: number = 22;
  7. }
  8. </script>

computed

如果是计算属性,这就要用到 getter 了。对于 Vue 中的计算属性,我们只需要将该计算属性名定义为一个函数,并在函数前加上 get 关键字即可。原来 Vue 中的 computed 里的每个计算属性都变成了在前缀添加 get 的函数。
例子:

  1. <script lang="ts">
  2. import { Vue, Component } from 'vue-property-decorator';
  3. @Component({})
  4. export default class "组件名" extends Vue {
  5. get ValA() {
  6. return 1;
  7. }
  8. }
  9. </script>

@Component

Component装饰器它注明了此类为一个 Vue 组件,因此即使没有设置选项也不能省略。如果需要定义比如 name、components、filters、directives以及自定义属性,就可以在Components装饰器中定义。
例子:

  1. import { Vue, Component } from 'vue-property-decorator'
  2. import MainHeader from './header.vue'
  3. import MainContent from './content.vue'
  4. @Component({
  5. components: {
  6. MainHeader,
  7. MainContent
  8. }
  9. })
  10. export default class Test extends Vue {}

@Prop

我们在使用 Vue 时有时会遇到子组件接收父组件传递来的参数,我们需要定义 Prop 属性。 @Prop 接受一个参数可以是类型变量或者对象或者数组。 @Prop 接受的类型比如 NumberJavaScript 的类型,之后定义的属性类型则是 TypeScript 的类型。
具体使用方法可以看下面例子:

  1. export default {
  2. props: {
  3. propA: {
  4. type: Number
  5. },
  6. propB: {
  7. default: 'default value'
  8. },
  9. propC: {
  10. type: [String, Boolean]
  11. }
  12. }
  13. }
  14. // 用 @Prop 实现上面的代码
  15. <script lang="ts">
  16. import { Vue, Component, Prop } from 'vue-property-decorator';
  17. @Component({})
  18. export default class "组件名" extends Vue {
  19. // 注意要加非空断言符 ! ,不然会报错。
  20. @Prop(Number)
  21. propA!: number;
  22. @Prop({default: 'default vale'})
  23. propB!: string;
  24. @Prop([String, Boolean])
  25. propC!: string | boolean;
  26. }
  27. </script>

注意:这里 ! 和可选参数 ? 是相反的, ! 告诉 TypeScript 这里一定有值。

@Watch

我们可以利用 vue-property-decorator 提供的 @Watch 装饰器来替换 Vue 中的 watch 属性,以此来监听值的变化。
@Watch 使用非常简单,接受第一个参数为要监听的属性名,第二个属性为可选对象。@Watch 所装饰的函数即监听到属性变化之后的操作。
举个例子来说明 @Watch 怎么用:

  1. export default {
  2. watch: {
  3. 'child': this.onChangeValue,
  4. 'person': {
  5. handler: 'onChangeValue',
  6. immediate: true,
  7. deep: true
  8. }
  9. },
  10. methods: {
  11. onChangeValue(newVal, oldVal) {
  12. // ...
  13. }
  14. }
  15. }
  16. // 在 vue-property-decorator 中 @Watch 的用法
  17. import { Vue, Component, Watch } from 'vue-property-decorator';
  18. @Watch('child')
  19. onChangeValue(newVal: string, oldVal: string) {
  20. // ...
  21. }
  22. @Watch('person', { immediate: true, deep: true })
  23. onChangeValue(newVal: Person, oldVal: Person) {
  24. // ...
  25. }

@Emit

关于 Vue 中的事件的监听与触发, Vue 提供了两个函数 $emit$on 。在 vue-property-decorator 中使用 @Emit 装饰器。
在 JavaScirpt 中如下:

  1. import Vue from 'vue';
  2. export default {
  3. mounted() {
  4. this.$on('emit-todo', function(n) {
  5. console.log(n)
  6. })
  7. this.emitTodo('world');
  8. },
  9. methods: {
  10. emitTodo(n) {
  11. console.log('hello');
  12. this.$emit('emit-todo', n);
  13. }
  14. }
  15. }

在 TypeScript 中如下:

  1. import { Vue, Component, Emit } from 'vue-property-decorator';
  2. @Component({})
  3. export default class Some extends Vue {
  4. mounted() {
  5. this.$on('emit-todo', function(n) {
  6. console.log(n);
  7. })
  8. this.emitTodo('world');
  9. }
  10. @Emit()
  11. emitTodo(n: string) {
  12. console.log('hello');
  13. }
  14. }

@Emit 装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杆式写法)的事件,并将其函数传递给 $emit 。如果我们想触发特定的事件呢,比如在 emitTodo 下触发 reset 事件:

  1. import { Vue, Component, Emit } from 'vue-property-decorator';
  2. @Component({})
  3. export default class "组件名" extends Vue {
  4. @Emit('reset')
  5. emitTodo(n: string) {
  6. // ...
  7. }
  8. }

我们只需要给装饰器 @Emit 传递一个事件名参数 reset ,这样函数 emitTodo 运行之后就会触发 reset 事件。

@Emit 修饰的函数所接受的参数会在运行之后触发事件的时候传递过去。

@Emit 触发事件有两种写法:

  • @Emit 不传参数,那么它触发的事件名就是它所修饰的函数名。
  • @Emit(name: string) 里面传递一个字符串,该字符串为要触发的事件名。

Mixins

vue-class-component 能够实现多混入,写法类似类继承。

  1. // mixin1.ts
  2. import Vue from 'vue'
  3. export default Vue.extend({
  4. data() {
  5. return {
  6. valFromMixin1: 'test'
  7. }
  8. }
  9. })
  10. // 不能是下面这种写法会报 Mixin1 is not a constructor function type
  11. export default {
  12. data() {
  13. return {
  14. valFromMixin1: 'test'
  15. }
  16. }
  17. }
  18. // mixin2.ts
  19. import { Component, Vue } from 'vue-property-decorator'
  20. @Component
  21. export default class Mixin2 extends Vue {
  22. methodFromMixin2() {}
  23. }
  24. // test.ts
  25. import Mixin1 from './mixin1'
  26. import Mixin2 from './mixin2'
  27. import { Component, Mixins } from 'vue-property-decorator'
  28. export default class Test extends Mixins(Mixin1, Mixin2) {
  29. test() {
  30. this.methodFromMixin2()
  31. console.log(this.valFromMixin1)
  32. }
  33. }
  34. // 如果只混入一个的话,可以这样写
  35. export default class Test extends Mixin1 {}
  36. export default class Test extends Mixin2 {}

这样写不仅不会报错,而且编辑器还有提示。

Vue Router

在 router/index.ts 内为变量添加相应 interface 即可:
在 vue-router 中 routes 的类型定义
类型:Array<RouteConfig>

  1. declare type RouteConfig = {
  2. path: string;
  3. component?: Component;
  4. name?: string; // for named routes (命名路由)
  5. components?: { [name: string]: Component }; // for named views (命名视图组件)
  6. redirect?: string | Location | Function;
  7. alias?: string | Array<string>;
  8. children?: Array<RouteConfig>; // for nested routes (嵌套路由)
  9. beforeEnter?: (to: Route, from: Route, next: Function) => void;
  10. meta?: any;
  11. }

在 router/index.ts 里需要加的代码:

  1. import Vue from 'vue';
  2. import VueRouter, { RouteConfig } from 'vue-router';
  3. import Layout from '@/views/Layout/index.vue';
  4. Vue.use(VueRouter);
  5. const routes: RouteConfig[] = [
  6. {
  7. path: "/",
  8. name: "layout",
  9. component: Layout
  10. },
  11. // ...其他routers
  12. ]
  13. const router = new VueRouter({
  14. routes
  15. });
  16. export default router;

如果像在组件内使用 Vue Router 导航钩子,必须注册一次:

  1. import { Component } from "vue-property-decorator";
  2. Component.registerHooks([
  3. "beforeRouteEnter", // 进入路由之前
  4. "beforeRouteLeave", // 离开路由之前
  5. "beforeRouteUpdate"
  6. ]);

值得注意的是,目前 vue2.x 虽然对 TypeScript 的支持已经有了一定进步,但是整体的代码提示还是不到位,仍然存在一些问题。 vue3.0 要到2020年一季度发布,这个版本的 vue 只是个过渡,想要让 vue 更好的与 typescript 结合,我们可以期待 vue3.0 的到来。

参考文章: