Vue 装饰器写法

11,046 阅读7分钟

依赖库介绍

vue-class-component(官方文档)

Vue的官方库,支持以class的方式来写vue代码,它支持下列写法

  • methods可以直接声明类的成员方法
  • 计算属性可以被声明为类的属性访问器
  • 初始化的data可以被声明为类属性
  • datarender已经所有的Vue生命周期钩子可以作为类的成员方法
  • 提供的装饰器Component和处理mixins写法的方式mixins
  • 所有其他属性需要放在装饰器Component

vue-property-derocator(官方文档)

Vue社区维护的在vue-class-component的基础上开发的库,它是对上一个库的拓展,添加了一下装饰器用来处理一些原本需要放在Component中的属性,下列是它提供的一下装饰器

  • @Prop
  • @PropSync
  • @Provide
  • @Model
  • @Watch
  • @Inject
  • @Emit
  • @Component(来自vue-class-component库的vue-class-component)
  • Mixins方法(来自vue-class-componentmixins)

vuex-class(官方文档)

Vue主要贡献者在vue-class-component基础上封装的针对vuex的装饰器写法的库。它提供了4个装饰器和namespace

  • @State
  • @Getter
  • @Mutation
  • @Action
  • namespace

装饰器使用

Js写法也可使用装饰器写法,用法相同,只是Ts语法部分会有不同;eslint规范也有可能报错,需要修改部分配置

安装

npm i --save vue-class-component
npm i --save vuex-class
npm i --save-dev vue-property-decorator

导入与使用

// 导入部分
import Component, { mixins } from 'vue-class-component'
// 如果用到了vue-property-decorator库,建议只使用一个库导入,避免Component在项目中频繁从两个不同的库中读取
import { Component, Prop, Vue } from 'vue-property-decorator'

// 使用部分,@Component必写
@Component
export default class HelloWorld extends Vue {
}

vue-class-component部分

datamethods计算属性生命周期钩子部分
  • 原写法
export default {
  data () {
    return {
      msg: 'msg'
    }
  },
  methods: {
    add () {}
  },
  mounted() {}, 
  computed: {
    newmsg () {
      return 'new ' + this.msg
    }
  }
}
  • 装饰器写法 data与methods直接定义为类的属性即可,computed需要定义为访问器属性,mixins方法原本通过mixins属性导入改为通过继承
@Component
export default class HelloWorld extends Vue {
  msg!: Number // data
  // 由于Ts最新版本使用了strictPropertyInitialization,如果变量没有在构造函数中使用或赋值,都需要添加!,进行显式赋值断言
  add () {} // methods
  get newmsg() { // computed
    return this.msg
  }
  mounted() {} // 生命周期钩子
}
mixins混入方法实现
  • 原写法
import mix from './mixin'
export default {
  mixins: [mix] // 通过mixins属性导入
}
  • 装饰器写法 通过继承装饰器提供的mixins方法处理后的混入方法mix实现
import Component, { mixins } from 'vue-class-component'
import mix from './mixin'

@Component
export class MyComp extends mixins(mix) {
  created () {
    console.log(this.mixinValue) // -> Hello
  }
}
其他的部分(componentsfliterdirectivesPropWatch等)
  • 原写法
export default {
  components: {},
  props: {},
  watch: {
    msg (newVal, oldVal) {}
  }
}
  • 装饰器写法 其余的部分全部包裹在@Component装饰器中
@Component({
  components: {},
  props: {},
  watch: {
    new (newVal, oldVal) {}
  }
})
export default class HelloWorld extends Vue {}

vue-property-decorator扩展的装饰器

@Component(options:ComponentOptions = {})

@Component装饰器来自vue-class-component,功能没有变化,在使用了vue-property-derocator后,主要用包括其尚不支持的componentdirectives等属性

import {Component,Vue} from 'vue-property-decorator'
import {componentA,componentB} from '@/components'

 @Component({
    components:{
        componentA,
        componentB,
    },
    directives: {
        focus: {
            // 指令的定义
            inserted: function (el) {
                el.focus()
            }
        }
    }
})
export default class YourCompoent extends Vue{}
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})

@Prop装饰器主要是处理prop属性接收的父组件传来的值

  • 接收三个参数
    • Constructor:如StringNumberBoolean等,指定Prop类型
    • Constructor[]:指定Prop的可选类型
    • PropOptionsProptypedefaultrequiredvalidator等配置
  • 原写法
export default{
    props:{
        propA:String, // propA:Number
        propB:[String,Number],
        propC:{
            type:Array,
            default:()=>{
                return ['a','b']
            },
            required: true,
            validator:(value) => {
                return [
                    'a',
                    'b'
                 ].indexOf(value) !== -1
        }
    }
  }
}

  • @Prop装饰器写法
import {Component,Vue,Prop} from vue-property-decorator;

@Component
export default class YourComponent extends Vue {
    @Prop(String)
    propA:string;
    
    @Prop([String,Number])
    propB:string|number;
    
    @Prop({
     type: String, // type: [String , Number]
     default: 'default value', // 一般为String或Number
      //如果是对象或数组的话。默认值从一个工厂函数中返回
      // defatult: () => {
      //     return ['a','b']
      // }
     required: true,
     validator: (value) => {
        return [
          'InProcess',
          'Settled'
        ].indexOf(value) !== -1
     }
    })
    propC:string 
}

@PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})

@ProSync装饰器与@Prop功能类似,但是最后@ProSync会生成一个新的计算属性

  • 接收两个参数
    • propName: string 表示父组件传递过来的属性名;
    • options: Constructor | Constructor[] | PropOptions 与@Prop的第一个参数一致
    • @PropSync需要配合父组件的.sync修饰符使用
  • 原写法
export default {
  props: {
    name: {
      type: String
    }
  },
  computed: {
    syncedName: {
      get() {
        return this.name
      },
      set(value) {
        this.$emit('update:name', value)
      }
    }
  }
}
  • @ProSync装饰器写法
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @PropSync('name', { type: String }) syncedName!: string
}

@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})

@Model装饰器是处理vuemodel属性,该属性可以再一个组件上自定义v-model,实现自定义组件prop属性的双向绑定

  • 接收两个参数
    • event: string 事件名。
    • options: Constructor | Constructor[] | PropOptions 与@Prop的第一个参数一致。

父级组件部分(template部分)

<my-input :value="value" @change="val=>value=val"></my-input>

myInput组件部分

  • 原写法(2.2.0+新增)
<template>
  <div class="hello">
    // 直接传值给父级方法后再父级中修改prop,避免了直接在组件中修改prop
    <input type="text" :value="value" @input="$emit('change', $event.target.value)" />
  </div>
</template>
export default {
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {
      type: String,
      default: ''
    }
  }
}
  • @Model装饰器写法
<template>
  <div class="hello">
    <input type="text" :value="value" @input="$emit('change', $event.target.value)" />
  </div>
</template>

import { Component, Model, Prop, Vue } from 'vue-property-decorator'

@Component
export default class HelloWorld extends Vue {
  @Model('change', {
    type: String
  })
  value!: String
}
@Watch(path: string, options: WatchOptions = {})

@Watch装饰器是处理vue的监听属性,监听发生在beforeCreate之后created之前

  • 接收两个参数
    • path: 监听的属性
    • options?: WatchOptions={},监听的配置信息
      • immediate?: boolean 侦听开始之后是否立即调用该回调函数;
      • deep?: boolean 被侦听的对象的属性被改变时,是否调用该回调函数;
  • 原写法
export default {
  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false
      }
    ],
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false
      }
    ]
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {}
  }
}
  • @Watch装饰器写法
import { Vue, Component, Watch } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}

  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}

  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
}
@Emit(event?: string)

@Emit装饰器专门处理$emit方法($emit方法并没有规定一定要在这里使用)

  • 接收的参数
    • 接受一个可选参数,该参数是$Emit的第一个参数,充当事件名。如果没有提供这个参数,$Emit会将回调函数名的camelCase转为kebab-case,并将其作为事件名
    • @Emit会将回调函数的返回值作为第二个参数,如果返回值是一个Promise对象,$emit会在Promise对象被标记为resolved之后触发
    • @Emit的回调函数的参数,会放在其返回值之后,一起被$emit当做参数使用。
  • 原写法
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    addToCount(n) {
      this.count += n
      this.$emit('add-to-count', n)
    },
    resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    returnValue() {
      this.$emit('return-value', 10)
    },
    onInputChange(e) {
      this.$emit('on-input-change', e.target.value, e)
    },
    promise() {
      const promise = new Promise(resolve => {
        setTimeout(() => {
          resolve(20)
        }, 0)
      })
      promise.then(value => {
        this.$emit('promise', value)
      })
    }
  }
}
  • 装饰器写法
import { Vue, Component, Emit } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  count = 0
  @Emit()
  addToCount(n: number) {
    this.count += n
  }
  @Emit('reset')
  resetCount() {
    this.count = 0
  }
  @Emit()
  returnValue() {
    return 10
  }
  @Emit()
  onInputChange(e) {
    return e.target.value
  }
  @Emit()
  promise() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(20)
      }, 0)
    })
  }
}

@Ref(refKey?: string)

@Refthis.$ref写法的封装,接收一个可选参数,用来指向元素或子组件的引用信息。如果没有提供这个参数,会使用装饰器后面的属性名充当参数

  • 原写法
export default {
  computed: {
    loginForm: {
      cache: false,
      get() {
        return this.$refs.loginForm
      }
    },
    passwordForm: {
      cache: false,
      get() {
        return this.$refs.changePasswordForm
      }
    }
  }
}
  • 装饰器写法
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'

@Componentexport default class MyComponent extends Vue {
  @Ref() readonly loginForm!: Form
  @Ref('changePasswordForm') readonly passwordForm!: Form

  public handleLogin() {
    this.loginForm.validate(valide => {
      if (valide) {
        // login...
      } else {
        // error tips
      }
    })
  }
}

@Provide/ @Inject

这两个装饰器是对ProvideInject写法的封装

  • Provide与Inject

    • 当父组件不便于向子组件传递数据,就把数据通过Provide传递下去,然后子组件通过Inject来获取
    • 以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效
    • 一旦父级注入的属性修改,子组件无法获取变化
  • 原写法

const symbol = Symbol('baz')

export const MyComponent = Vue.extend({
  inject: {
    foo: 'foo',
    bar: 'bar',
    optional: { from: 'optional', default: 'default' },
    [symbol]: symbol
  },
  data() {
    return {
      foo: 'foo',
      baz: 'bar'
    }
  },
  provide() {
    return {
      foo: this.foo,
      bar: this.baz
    }
  }
})
  • 装饰器写法
import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
const symbol = Symbol('baz')

@Component
export class MyComponent extends Vue {
  @Inject() readonly foo!: string
  @Inject('bar') readonly bar!: string
  @Inject({ from: 'optional', default: 'default' }) readonly optional!: string
  @Inject(symbol) readonly baz!: string

  @Provide() foo = 'foo'
  @Provide('bar') baz = 'bar'
}
@ProvideReactive/ @InjectReactive

这两个装饰器的功能与@Provide/ @Inject类似,但是它注入的属性是响应式的,父级注入的属性一旦修改,子组件可以获取到变化

  • 装饰器写法
const key = Symbol()
@Component
class ParentComponent extends Vue {
  @ProvideReactive() one = 'value'
  @ProvideReactive(key) two = 'value'
}

@Component
class ChildComponent extends Vue {
  @InjectReactive() one!: string
  @InjectReactive(key) two!: string
}

vuex-class的使用

@namespace命名空间
  • 直接使用装饰器
@Component
class MyComponent extends Vue{
    @namespace('test').State foo!: string;
}
  • 引用装饰器赋值
const TestModule = namespace("test")

@Component
class MyComponent extends Vue{
    @TestModule.State foo!: string;
}
@State状态映射(数据)
  • 直接映射 @State foo 和 @State("foo") foo 相同
@Component
class MyComponent extends Vue{
    @namespace('test').State foo!: string;
}
  • 获取计算后的state,state映射到组件时,是放在computed下的,因此本身为计算属性
@Component
class MyComponent extends Vue{
    /* 只从 state 获取*/
    @namespace('test').State(state=>state.foo) foo!: string;
    /*从state和getters上获取*/
    @namespace('test').State((state,getters)=>state.foo+'getter:'+getters.getFoo) svsgfoo!: string;
}
  • 内部使用namespace 如果不想在外部使用namespace,可以使用参数传递namespace
@Component
class MyComponent extends Vue{
    @State("foo",{namespace:"test"}) foo!: string;
}
@Getter计算属性

和State类似,但不支持再次计算,只能获取值

@Component
class MyComponent extends Vue{
    @State("foo",{namespace:"test"}) foo!: string;
}
@Action异步计算,触发Mutation
@Component
class MyComponent extends Vue{
   @Action("test/setFoo") setFoo!: Function;
}

action可以配合promise + async/await 一同使用

// 原写法
actions:{
    async queryState({commit},token){
        let result = await Axios.get("url",{data:{ token }})
        commit('state',result.data)
        return result.data
    }
}

// 装饰器写法
@Component
class MyComponent extends Vue{
   private token:string="best";
   @Action queryState!: Function;
   
   async onQueryStateClick(){
       let data = await this.queryState(this.token)
       // todo ...
   }
}
@Mutation直接计算state数据
@Component
class MyComponent extends Vue{
   @Action("test/setFoo") setFoo!: Function;
}
原代码
export default new Vuex.Store({
  state: { /* 状态库 */ },
  mutations: { /* 同步计算库 使用commit触发 */ },
  actions: { /* 异步计算库 使用dispatch触发 */ },
  getters: { /* 计算属性  */ },
  modules: { /* 分模块 */
    test: {
      namespaced: true, /* 开启module模式 */
      state: {
        foo: "this is foo"
      },
      getters: {
        getFoo(state) { 
          return state.foo;
        }
      },
      mutations: {
        setFoo(state, foo) {
          state.foo = foo;
        }
      },
      actions: {
        setFoo({ commit }, foo) {
          setTimeout(() => {
            commit('setFoo',foo)
          }, 1e3);
        }
      }
    }
  }
})