# Vue Property Decorator
此文的示例都来自 Vue Property Decorator (opens new window),只是增加了一些自己的理解,作为基础学习文档
主要罗列一下 vue
装饰器的常用方法。官方文档共有以下装饰器/Mixin
@Prop
@PropSync
@Model
@ModelSync
@Watch
Computed
@Provide
@Inject
@ProvideReactive
@InjectReactive
@Emit
@Ref
@VModel
@Component (provided by vue-class-component)
Mixins (the helper function named mixins provided by vue-class-component)
如何使用:
Vue Property Decorator
主要用于 vue2.x + ts
。创建 vue
项目的时候,选择 class-style component syntax
,就会默认安装好 vue-class-component
和 vue-property-decorator
# @Prop
语法:@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
,不用特意去记,跟原 Props
的参数基本一致,只是写法稍微变了一点
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Prop(Number) readonly propA: number | undefined
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
// 注意不能直接用下面的方式去定义默认值
@Prop() prop = 'default value' // ×
对应的源代码为:
export default {
props: {
propA: {
type: Number
},
propB: {
default: "default value"
},
propC: {
type: [String, Boolean]
}
}
}
# @PropSync
语法:@PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})
,跟 vue2
中的 .sync
修饰符实现的效果类似,但是不太建议使用这种方式,会破坏 vue
组件单向数据流的设定
import { Vue, Component, PropSync } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@PropSync('name', { type: String }) syncedName!: string
}
对应的源代码为:
export default {
props: {
name: {
type: String
}
},
computed: {
syncedName: {
get() {
return this.name
},
set(value) {
this.$emit("update:name", value)
}
}
}
}
如上,在父组件传递一个 name
参数,在子组件,@PropSync
定义了一个计算属性 syncedName
,并且设置了 setter
和 getter
,在访问 syncedName
时,返回父组件传递过来的 name
值,在设置 syncedName
时,emit
一个更新事件给父组件。而在父组件,也只需要用 .sync
修饰符:<parentComponent :name.sync="name"></parentComponent>
。后续也只需要在子组件操作 syncedName
变量即可与父组件的变量 name
同步
# @Model
语法:@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
。先看代码
import { Vue, Component, Model } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Model('change', { type: Boolean }) readonly checked!: boolean
}
// 不要忘了同步数据时需要 $emit 一下,事件对应@Model中的事件
syncValue(): void {
this.$emit('change', someValue)
}
对应的源代码为:
export default {
model: {
prop: "checked",
event: "change"
},
props: {
checked: {
type: Boolean
}
}
}
@Model
这一项对应的是:在组件中使用 v-model
中的 model
选项
一般情况下,v-model
会默认利用名为 value
的 prop
和名为 input
的事件(最常用)。但对于单选框、复选框这种输入控件,便不太符合场景。因此就有了 model
选项。具体示例可以看下上面的源代码,或更为详细的 vue
官方文档:自定义组件的 v-model (opens new window)
通过 Model
选项,我们可以自定义地修改子组件接收的 prop
(如上源代码中的 checked
变量),并且修改触发的事件(如上源码中的 input
事件修改为 change
),这样就可以自由控制什么时候将数据“同步”给父组件
# @ModelSync
语法:@ModelSync(propName: string, event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
,与 @Model
想比较之下,@ModelSync
会自动 $emit
对应事件给父组件,使用起来应该会更舒服一些。不过说实话,基本没咋用过 model
选项 em...
import { Vue, Component, ModelSync } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@ModelSync('checked', 'change', { type: Boolean })
readonly checkedValue!: boolean
}
// 现在修改 checkedValue 会自动 $emit 给父组件
对应的源代码为:
export default {
model: {
prop: "checked",
event: "change"
},
props: {
checked: {
type: Boolean
}
},
computed: {
checkedValue: {
get() {
return this.checked
},
set(value) {
this.$emit("change", value)
}
}
}
}
# @Watch
语法:@Watch(path: string, options: WatchOptions = {})
,也就是监听器 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) {}
@Watch("person")
@Watch("child")
onPersonAndChildChanged() {}
}
对应的源代码为:
export default {
watch: {
child: [
{
handler: "onChildChanged",
immediate: false,
deep: false
},
{
handler: "onPersonAndChildChanged",
immediate: false,
deep: false
}
],
person: [
{
handler: "onPersonChanged1",
immediate: true,
deep: true
},
{
handler: "onPersonChanged2",
immediate: false,
deep: false
},
{
handler: "onPersonAndChildChanged",
immediate: false,
deep: false
}
]
},
methods: {
onChildChanged(val, oldVal) {},
onPersonChanged1(val, oldVal) {},
onPersonChanged2(val, oldVal) {},
onPersonAndChildChanged() {}
}
}
# Computed
computed
选项已经被替代为 getter
、setter
import Vue from "vue"
import Component from "vue-class-component"
@Component
export default class HelloWorld extends Vue {
firstName = "John"
lastName = "Doe"
// Declared as computed property getter
get name() {
return this.firstName + " " + this.lastName
}
// Declared as computed property setter
set name(value) {
const splitted = value.split(" ")
this.firstName = splitted[0]
this.lastName = splitted[1] || ""
}
}
# @Provide、@Inject
语法:@Provide(key?: string | symbol) / @Inject(options?: { from?: InjectKey, default?: any } | InjectKey)
,先看代码
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'
}
对应的源代码为:
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
}
}
})
在 vue2.x
中,于本人来说 Provide/Inject
用的是相对比较少的,一方面不会使用层级过深的组件,另一方面 Provide/Inject
也具有其一定的缺陷:依赖注入 (opens new window);在 vue3.x
中,可以用来全局注入,如 vue2.x
中的 Vue.prototype
(vue3.x
中已被废除,Provide/Inject
作为替代方案:查看文档 (opens new window))
# @Emit
语法:@Emit(event?: string)
,直接看代码,熟悉熟悉就可以上手的了
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)
})
}
}
对应的源代码为:
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)
})
}
}
}
# Vuex-Class
vuex
也有对应的装饰器,用起来比较舒服,不用写那么长的代码了:this.$store.state.user.userInfo
...
官网地址:vuex-class (opens new window)
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']
}
}
# vuex-module-decorators 起步
新增 vuex-module-decorators
,文档及示例地址为:'vuex-module-decorators (opens new window)
State
@Mutation
Getter
@Action
@MutationAction
Dynamic Module
# State
vuex-module-decorators
取消了 State
选项,跟 vue3
一样,直接定义写变量即可。
import { Module, VuexModule } from "vuex-module-decorators"
@Module
export default class Vehicle extends VuexModule {
wheels = 2
}
对应的源代码为:
export default {
state: {
wheels: 2
}
}
# @Mutation
当使用 Mutation
装饰器时,使用 this
修改 State
数据。同时,在 Mutation
中避免使用异步函数以及箭头函数
import { Module, VuexModule, Mutation } from "vuex-module-decorators"
@Module
export default class Vehicle extends VuexModule {
wheels = 2
@Mutation
puncture(n: number) {
this.wheels = this.wheels - n
}
}
对应的源代码为:
export default {
state: {
wheels: 2
},
mutations: {
puncture: (state, payload) => {
state.wheels = state.wheels - payload
}
}
}
# Getter
此时的 Getter
不再是 store
的一个选项,而是成为了 es6
的 getter
函数
import { Module, VuexModule } from "vuex-module-decorators"
@Module
export default class Vehicle extends VuexModule {
wheels = 2
get axles() {
return this.wheels / 2
}
}
对应的源代码为:
export default {
state: {
wheels: 2
},
getters: {
axles: state => state.wheels / 2
}
}
# @Action
如果需要在 Action
执行长时间运行的任务,建议将其定义为异步函数(async-await
)。如果不这么做的话,vuex-module-decorators
也会将你的函数包装成一个 Promise
函数并等待它;同时也不要将其定义为箭头函数,因为需要重新绑定 Actions
这里稍微注意一下,在 Action
中需要修改 state
的话,还是需要通过提交 Mutation
。而现在需要使用 this.context.commit()
提交
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators"
import { get } from "request"
@Module
export default class Vehicle extends VuexModule {
wheels = 2
@Mutation
addWheel(n: number) {
this.wheels = this.wheels + n
}
@Action
async fetchNewWheels(wheelStore: string) {
const wheels = await get(wheelStore)
this.context.commit("addWheel", wheels)
}
}
对应的源代码为:
const request = require("request")
export default {
state: {
wheels: 2
},
mutations: {
addWheel: (state, payload) => {
state.wheels = state.wheels + payload
}
},
actions: {
fetchNewWheels: async (context, payload) => {
const wheels = await request.get(payload)
context.commit("addWheel", wheels)
}
}
}
# @MutationActions
如果当前有一个异步操作,并且获得的结果需要提交到 state
,那么可以使用 @MutationActions
。需要注意,当其返回的是 undefined
时,不会提交到 state
import {VuexModule, Module, MutationAction} from 'vuex-module-decorators'
@Module
class TypicodeModule extends VuexModule {
posts: Post[] = []
users: User[] = []
@MutationAction
async function updatePosts() {
const posts = await axios.get('https://jsonplaceholder.typicode.com/posts')
return { posts }
}
}
对应的源代码为:
const typicodeModule = {
state: {
posts: [],
users: []
},
mutations: {
updatePosts: function(state, posts) {
state.posts = posts
}
},
actions: {
updatePosts: async function(context) {
const posts = await axios.get("https://jsonplaceholder.typicode.com/posts")
context.commit("updatePosts", posts)
}
}
}
# Dynamic Module
使用 vuex-module-decorators
之后,可以使用动态 Modules
。动态 Modules
在注册时,跟一般的 vuex
不一样,在 @store/index.ts
中,只需要 new
一个空的 Vuex.Store({})
// Dynamic Modules
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
// Declare empty store first, dynamically register all modules later.
export default new Vuex.Store({})
在具体的 module
下,需要注意:
- 指定
module
为动态module
- 必须要指定
name
- 需要通过
getModule
将模块export
出去
import { Module, VuexModule, getModule } from "vuex-module-decorators"
import store from "@/store"
@Module({
namespaced: true,
dynamic: true,
store,
name: "User"
})
export default class User extends VuexModule {
userInfo = {
name: "wolfBerry",
phone: "18819490370",
email: "906368017@qq.com"
}
}
export const UserModule = getModule(User)
最后就可以在页面上,通过 import
引入后再使用
import { UserModule } from "@/store/module/user"
import { TestModule } from "@/store/module/test"
@Component
export default class Home extends Vue {
created() {
console.log(UserModule.userInfo)
}
handleChange() {
TestModule.SET_COUNT(2)
}
}
# 结尾
目前 vuex-module-decorator
不支持动态模块和嵌套模块混合使用,只能使用其中一种
vuex-module-decorator
、vuex-class
、vue-class-component
三者熟练使用起来可以省掉很多代码,真的是谁用谁舒服,并且可以让代码看起来更 “高级” 一些哈!!!