vue-class-component

vue-class-component是一个支持es6 class风格来开发组件的vue官方库,并使vue组件可以使用继承、混入等特性。

// App.vue
<script>
import Vue from 'vue'
import Component from 'vue-class-component'
 
@Component({
  props: {
    propMessage: String
  }
})
export default class App extends Vue {
  // initial data
  msg = 123
 
  // use prop values for initial data
  helloMsg = 'Hello, ' + this.propMessage
 
  // lifecycle hook
  mounted () {
    this.greet()
  }
 
  // computed
  get computedMsg () {
    return 'computed ' + this.msg
  }
 
  // method
  greet () {
    alert('greeting: ' + this.msg)
  }
}
</script>

// main.js
import App from './App.vue'

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

在这个例子中,很容易发现几个疑点:

  1. App类居然没有constructor构造函数;

  2. 导出的类居然没有被new就直接使用了。

  3. msg=123,这是什么语法?

这里摘取自知乎某位大佬的一篇文章,里面有详细的解释,以及源码实现,不做过多解释。【源码探秘】vue-class-component

vue-property-decorator

vue-property-decorator是基于vue-class-component扩展的一个非官方库, 主要功能就和名字一样,为vue的属性提供装饰器写法,让代码更加美观、直白。
主要提供了以下装饰器和一个mixin方法:

  • @Prop
  • @PropSync
  • @Model
  • @Watch
  • @Provide
  • @Inject
  • @ProvideReactive
  • @InjectReactive
  • @Emit
  • @Ref
  • @Component (provided by vue-class-component)
  • Mixins (the helper function named mixins provided by vue-class-component)

@Prop(options: (PropOptions | Constructor[] | Constructor) = {}) decorator

import { Vue, Component, Prop } from 'vue-property-decorator'
 
@Component
export default class YourComponent extends Vue {
  @Prop(Number) propA
  @Prop({ default: 'default value', type: Number }) propB
  @Prop([String, Boolean]) propC

解析后

export default {
  props: {
    propA: {
      type: Number,
    },
    propB: {
      default: 'default value',
      type: Number
    },
    propC: {
      type: [String, Boolean],
    },
  },
}

到这里可以发现,上一个案例中Vue、Component 也可以通过 vue-property-decorator 引入。

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

import { Vue, Component, PropSync } from 'vue-property-decorator'
 
@Component
export default class YourComponent extends Vue {
  @PropSync('name', { type: String }) syncedName
}

解析后

export default {
  props: {
    name: {
      type: String,
    },
  },
  computed: {
    syncedName: {
      get() {
        return this.name
      },
      set(value) {
        this.$emit('update:name', value)
      },
    },
  },
}

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

import { Vue, Component, Model } from 'vue-property-decorator'
 
@Component
export default class YourComponent extends Vue {
  @Model('change', { type: Boolean }) checked
}

解析后

export default {
  model: {
    prop: 'checked',
    event: 'change',
  },
  props: {
    checked: {
      type: Boolean,
    },
  },
}

自定义v-model,Vue组件提供 model: { prop?: string, event?: string } 让我们可以定制prop和event。默认情况下,一个组件上的v-model 会把 value用作 prop且把 input用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop来达到不同的目的。使用model选项可以回避这些情况产生的冲突。

  • 下面是Vue官网的例子
Vue.component('my-checkbox', {
    model: {
        prop: 'checked',
        event: 'change'
    }, 
    props: {    // this allows using the `value` prop for a different purpose  
        value: String,
        checked: { // use `checked` as the prop which take the place of `value`
            type: Number, default: 0
        }
    }, 
    methods: {
        isChange() {
            this.$emit('change', val);
        },
    }
})
<template>
  <my-checkbox v-model="foo" value="some value"></my-checkbox>
</template>

上述代码相当于:

<template>
  <my-checkbox  :checked="foo"  @change="val => { foo = val }"  value="some value" />
</template>

@Watch(path: string, options: WatchOptions = {}) decorator

import { Vue, Component, Watch } from 'vue-property-decorator'
 
@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val, oldVal) {}
 
  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val, oldVal) {}
 
  @Watch('person')
  onPersonChanged2(val, oldVal) {}
}

解析后

export default {
  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false,
      },
    ],
    person: [  // 这里需要明白的是watch可以接受多个处理函数
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true,
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false,
      },
    ],
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {},
  },
}

@Provide(key?: string | symbol) / @Inject(options?: { from?: InjectKey, default?: any } | InjectKey) decorator

import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
 
const symbol = Symbol('baz')
 
@Component
export class MyComponent extends Vue {
  @Inject() foo
  @Inject('bar') bar
  @Inject({ from: 'optional', default: 'default' }) optional
  @Inject(symbol) baz
 
  @Provide() foo = 'foo'
  @Provide('bar') baz = 'bar'
}

解析后

const symbol = Symbol('baz')
 
export const MyComponent = Vue.extend({
  inject: {
    foo: 'foo',
    bar: 'bar',
    optional: { from: 'optional', default: 'default' },
    baz: symbol,
  },
  data() {
    return {
      foo: 'foo',
      baz: 'bar',
    }
  },
  provide() {
    return {
      foo: this.foo,
      bar: this.baz,
    }
  },
})

注意:vue官方说provide/inject不是响应式的,但是是对基础类型而言,如果提供的值是一个对象是可以监听到的。原因官方没说,猜测是provide的只是对象的引用地址,而不是拷贝一个新对象,所以修改值的时候就会变成响应式。

@ProvideReactive(key?: string | symbol) / @InjectReactive(options?: { from?: InjectKey, default?: any } | InjectKey) decorator

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
}

这两个修饰器是@Provide 和 @Inject 的响应式版本。所有被提供的值能实现双向绑定。

@Emit(event?: string) decorator

import { Vue, Component, Emit } from 'vue-property-decorator'
 
@Component
export default class YourComponent extends Vue {
  count = 0
 
  @Emit()
  addToCount(n) {
    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)
    })
  }
}

解析后

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)
      })
    },
  },
}

$emit 装饰的函数,它们的返回值后面跟着它们的原始参数。如果返回值是一个Promise对象,则会在触发前达到完成状态。

如果事件名称不提供 event 参数,函数名将会被代替使用。 在这种情况下,,驼峰命名将被转换成短横线隔开式命名。

@Ref(refKey?: string) decorator

import { Vue, Component, Ref } from 'vue-property-decorator'
 
import AnotherComponent from '@/path/to/another-component.vue'
 
@Component
export default class YourComponent extends Vue {
  @Ref() readonly anotherComponent!: AnotherComponent
  @Ref('aButton') readonly button!: HTMLButtonElement
}

解析后

export default {
  computed() {
    anotherComponent: {
      cache: false,
      get() {
        return this.$refs.anotherComponent as AnotherComponent
      }
    },
    button: {
      cache: false,
      get() {
        return this.$refs.aButton as HTMLButtonElement
      }
    }
  }
}

@ref 是用于引用实际的 DOM 元素或者子组件,用 ref 比 document 拿要方便很多,使用场景不详。

另外可能你也发现了,components、 filters、 directives、mixins等未提供装饰符的属性怎么办,接下来就是主角登场了,所有不知道怎么写的,就写它里面。

@Component(options)

import { Component, Vue } from 'vue-property-decorator'
import Gap from './Gap.vue'

@Component({
    components: {
        Gap,
    },
    directives: {
		directive1: {
			inserted: function (el) {
				//
			}
		}
	},
	filters: {
		filter1() {
			return 1;
		}
	}
})

注意@Component的功能不是vue-property-decorator提供的,而是vue-class-component 。

vuex-class

同样的vuex-class可以为vuex的属性提供装饰器写法,让代码更加美观、直白。

  • 官方案例:
import Vue from 'vue'
import Component from 'vue-class-component'
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from 'vuex-class'

const someModule = namespace('path/to/module')

@Component
export class MyComp extends Vue {
  @State('foo') stateFoo
  @State(state => state.bar) stateBar
  @Getter('foo') getterFoo
  @Action('foo') actionFoo
  @Mutation('foo') mutationFoo
  @someModule.Getter('foo') moduleGetterFoo

  // If the argument is omitted, use the property name
  // for each state/getter/action/mutation type
  @State foo
  @Getter bar
  @Action baz
  @Mutation qux

  created () {
    this.stateFoo // -> store.state.foo
    this.stateBar // -> store.state.bar
    this.getterFoo // -> store.getters.foo
    this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
    this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
    this.moduleGetterFoo // -> store.getters['path/to/module/foo']
  }
}

本文到此就结束了,如果有帮助到你就点个赞呗~

Logo

前往低代码交流专区

更多推荐