搭建vue-cli时的配置:
打开项目,发现最大的不同是额外引入了一个库 vue-property-decorator 以及多了三个文件: shims-tsx.d.ts、shims-vue.d.ts 和 tsconfig.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.component 或 Vue.extend 定义组件。这种方式基本无学习成本,直接在现有的写法上结合 typescript 语法即可。但是这种方法不能完全实现 mixins 多混入的效果,只能混入一个。不推荐用这种方式写,无法实现多继承。
还有一种方式是 基于类的 Vue 组件,学习成本较高,需要对类有一定的了解。主要用到 vue-property-decorator ,下面重点介绍这种方式:
vue-property-decorator 主要提供了多个装饰器和一个函数:
- @Prop
- @PropSync
- @Model
- @Watch
- @Provide
- @Inject
- @ProvideReactive
- @InjectReactive
- @Emit
- @Ref
- @Component(由 vue-class-component 提供)
- Mixins(由 vue-class-component 提供)
data
对于 data 里的变量定义,我们可以直接按 typescript 定义类变量的写法写就可以。
例子:
<script lang="ts">import { Vue, Component } from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue {name: string = "huangry";age: number = 22;}</script>
computed
如果是计算属性,这就要用到 getter 了。对于 Vue 中的计算属性,我们只需要将该计算属性名定义为一个函数,并在函数前加上 get 关键字即可。原来 Vue 中的 computed 里的每个计算属性都变成了在前缀添加 get 的函数。
例子:
<script lang="ts">import { Vue, Component } from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue {get ValA() {return 1;}}</script>
@Component
Component装饰器它注明了此类为一个 Vue 组件,因此即使没有设置选项也不能省略。如果需要定义比如 name、components、filters、directives以及自定义属性,就可以在Components装饰器中定义。
例子:
import { Vue, Component } from 'vue-property-decorator'import MainHeader from './header.vue'import MainContent from './content.vue'@Component({components: {MainHeader,MainContent}})export default class Test extends Vue {}
@Prop
我们在使用 Vue 时有时会遇到子组件接收父组件传递来的参数,我们需要定义 Prop 属性。 @Prop 接受一个参数可以是类型变量或者对象或者数组。 @Prop 接受的类型比如 Number 是 JavaScript 的类型,之后定义的属性类型则是 TypeScript 的类型。
具体使用方法可以看下面例子:
export default {props: {propA: {type: Number},propB: {default: 'default value'},propC: {type: [String, Boolean]}}}// 用 @Prop 实现上面的代码<script lang="ts">import { Vue, Component, Prop } from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue {// 注意要加非空断言符 ! ,不然会报错。@Prop(Number)propA!: number;@Prop({default: 'default vale'})propB!: string;@Prop([String, Boolean])propC!: string | boolean;}</script>
注意:这里 ! 和可选参数 ? 是相反的, ! 告诉 TypeScript 这里一定有值。
@Watch
我们可以利用 vue-property-decorator 提供的 @Watch 装饰器来替换 Vue 中的 watch 属性,以此来监听值的变化。@Watch 使用非常简单,接受第一个参数为要监听的属性名,第二个属性为可选对象。@Watch 所装饰的函数即监听到属性变化之后的操作。
举个例子来说明 @Watch 怎么用:
export default {watch: {'child': this.onChangeValue,'person': {handler: 'onChangeValue',immediate: true,deep: true}},methods: {onChangeValue(newVal, oldVal) {// ...}}}// 在 vue-property-decorator 中 @Watch 的用法import { Vue, Component, Watch } from 'vue-property-decorator';@Watch('child')onChangeValue(newVal: string, oldVal: string) {// ...}@Watch('person', { immediate: true, deep: true })onChangeValue(newVal: Person, oldVal: Person) {// ...}
@Emit
关于 Vue 中的事件的监听与触发, Vue 提供了两个函数 $emit 和 $on 。在 vue-property-decorator 中使用 @Emit 装饰器。
在 JavaScirpt 中如下:
import Vue from 'vue';export default {mounted() {this.$on('emit-todo', function(n) {console.log(n)})this.emitTodo('world');},methods: {emitTodo(n) {console.log('hello');this.$emit('emit-todo', n);}}}
在 TypeScript 中如下:
import { Vue, Component, Emit } from 'vue-property-decorator';@Component({})export default class Some extends Vue {mounted() {this.$on('emit-todo', function(n) {console.log(n);})this.emitTodo('world');}@Emit()emitTodo(n: string) {console.log('hello');}}
在 @Emit 装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杆式写法)的事件,并将其函数传递给 $emit 。如果我们想触发特定的事件呢,比如在 emitTodo 下触发 reset 事件:
import { Vue, Component, Emit } from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue {@Emit('reset')emitTodo(n: string) {// ...}}
我们只需要给装饰器 @Emit 传递一个事件名参数 reset ,这样函数 emitTodo 运行之后就会触发 reset 事件。
@Emit 修饰的函数所接受的参数会在运行之后触发事件的时候传递过去。
@Emit 触发事件有两种写法:
@Emit不传参数,那么它触发的事件名就是它所修饰的函数名。@Emit(name: string)里面传递一个字符串,该字符串为要触发的事件名。
Mixins
vue-class-component 能够实现多混入,写法类似类继承。
// mixin1.tsimport Vue from 'vue'export default Vue.extend({data() {return {valFromMixin1: 'test'}}})// 不能是下面这种写法会报 Mixin1 is not a constructor function typeexport default {data() {return {valFromMixin1: 'test'}}}// mixin2.tsimport { Component, Vue } from 'vue-property-decorator'@Componentexport default class Mixin2 extends Vue {methodFromMixin2() {}}// test.tsimport Mixin1 from './mixin1'import Mixin2 from './mixin2'import { Component, Mixins } from 'vue-property-decorator'export default class Test extends Mixins(Mixin1, Mixin2) {test() {this.methodFromMixin2()console.log(this.valFromMixin1)}}// 如果只混入一个的话,可以这样写export default class Test extends Mixin1 {}export default class Test extends Mixin2 {}
这样写不仅不会报错,而且编辑器还有提示。
Vue Router
在 router/index.ts 内为变量添加相应 interface 即可:
在 vue-router 中 routes 的类型定义
类型:Array<RouteConfig>
declare type RouteConfig = {path: string;component?: Component;name?: string; // for named routes (命名路由)components?: { [name: string]: Component }; // for named views (命名视图组件)redirect?: string | Location | Function;alias?: string | Array<string>;children?: Array<RouteConfig>; // for nested routes (嵌套路由)beforeEnter?: (to: Route, from: Route, next: Function) => void;meta?: any;}
在 router/index.ts 里需要加的代码:
import Vue from 'vue';import VueRouter, { RouteConfig } from 'vue-router';import Layout from '@/views/Layout/index.vue';Vue.use(VueRouter);const routes: RouteConfig[] = [{path: "/",name: "layout",component: Layout},// ...其他routers]const router = new VueRouter({routes});export default router;
如果像在组件内使用 Vue Router 导航钩子,必须注册一次:
import { Component } from "vue-property-decorator";Component.registerHooks(["beforeRouteEnter", // 进入路由之前"beforeRouteLeave", // 离开路由之前"beforeRouteUpdate"]);
值得注意的是,目前 vue2.x 虽然对 TypeScript 的支持已经有了一定进步,但是整体的代码提示还是不到位,仍然存在一些问题。 vue3.0 要到2020年一季度发布,这个版本的 vue 只是个过渡,想要让 vue 更好的与 typescript 结合,我们可以期待 vue3.0 的到来。
参考文章:
