前言:对于我们而言,typescript 更像一个工具
从 vue2.5 之后,vue 对 ts 有更好的支持。根据官方文档,vue 结合 typescript ,有两种书写方式:
Vue.extend
import Vue from 'vue' const Component = Vue.extend({ // type inference enabled })
Class-style Vue Components
import Vue from 'vue' import Component from 'vue-class-component' // The @Component decorator indicates the class is a Vue component @Component({ // All component options are allowed in here template: '<button @click="onClick">Click!</button>' }) export default class MyComponent extends Vue { // Initial data can be declared as instance properties message: string = 'Hello!' // Component methods can be declared as instance methods onClick (): void { window.alert(this.message) } }
理想情况下,Vue.extend 的书写方式,是学习成本最低的。在现有写法的基础上,几乎 0 成本的迁移
// 现在常见的写法export default { // your code }
但「理想丰满,现实骨感」,问题出在:
- Vue.exend 在和 vuex 和 mixins 结合使用的时候,无法发挥 ts 的作用,vuex 和 mixins 会在项目中大量使用,这个问题不能忽视。
Vue.extend + vuex 的问题
由于 vuex 使用 mapState, mapActions 等方法的时候,通过字符串形式做映射,这个过程中,丢失了类型信息。下面的 gif 可以看到,整个过程中:
- 无法做代码提示
- 无法对对应的 actions 和 state 做类型声明,使得类型检查生效
- 无法使用重构
显然,如果只有一部分的方法和属性得到了代码提示和类型检查,就是失去了使用 typescript 意义。
在 Vue.extend + vuex 写法下,这个问题暂时没有解决方案。
Vue.extend + mixins 的问题
同样的问题,在 mixin 中定义的方法,不会被 typescript 识别到,下面 gif 可以看到,不仅仅「代码提示」「类型检查」「代码重构」没有工作,甚至因识别不到 test 而报错
那么就剩下 Class-Style Components 方案。当然,这个方案需要做额外的工作才能够让「vue 全家桶 + ts」良好的工作。
原理:将属性直接挂载在 class 上,使得 typescript 能够良好的进行「代码提示」和「类型检查」。然后再通过装饰器将属性转成 vue 上的属性。
例如 @Prop, @Watch, @Action 等装饰器,将属性做相应的转换成 props, watch, mapActions 里面的值,具体后面例子展示。
vue-class-component
这里库提供最基础的 vue 装饰器:@Component 。其他的 vue 装饰器库,都在这个库的基础上做扩展和修改。看看官网的例子:
import Vue from 'vue'import Component from 'vue-class-component'// @Component 会将 MyComponent 中的属性,转换成 vue 对应的属性@Component({ // Vue 所有的属性都可以在这里声明,一般用到的比较少 template: '<button @click="onClick">Click!</button>'}) export default class MyComponent extends Vue { // @Component 将 message 转成成 data message: string = 'Hello!' // @Component 会将这里的 getter 属性,转换成 computed get name(){ return 'anders' } // @Component 识别到 created 是声明周期关键字,不做处理 created(){} // @Component 识别到 onClick 不是关键字,将它转成 methods onClick (): void { window.alert(this.message) } }
vue-property-decorator
这个库提供了:
- @Emit
- @Inject
- @Model
- @Prop
- @Provide
- @Watch
其中常用的: @Prop,@Watch,@Emit。 看例子:
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator'const s = Symbol('baz') @Component export class MyComponent extends Vue { @Emit() addToCount(n: number){ this.count += n } @Emit('reset') resetCount(){ this.count = 0 } @Prop() propA: number @Prop({ default: 'default value' }) propB: string @Prop([String, Boolean]) propC: string | boolean @Watch('child') onChildChanged(val: string, oldVal: string) { } @Watch('person', { immediate: true, deep: true }) onPersonChanged(val: Person, oldVal: Person) { } }
上面的使用就相当于:
const s = Symbol('baz') export const MyComponent = Vue.extend({ name: 'MyComponent', props: { checked: Boolean, propA: Number, propB: { type: String, default: 'default value' }, propC: [String, Boolean], }, methods: { addToCount(n){ this.count += n this.$emit("add-to-count", n) }, resetCount(){ this.count = 0 this.$emit("reset") }, onChildChanged(val, oldVal) { }, onPersonChanged(val, oldVal) { } }, watch: { 'child': { handler: 'onChildChanged', immediate: false, deep: false }, 'person': { handler: 'onPersonChanged', immediate: true, deep: true } } })
更加全面的用法参考文档:vue-property-decorator