vue-property-decorator 使用学习

  • Post author:
  • Post category:vue

一、安装与使用
一般的,vue 项目中使用 TypeScript 时,会安装 vue-property-decorator 这个库,使用装饰器简化书写。
安装:

npm i -S vue-property-decorator

使用:

// 当在vue单文件中使用TypeScript时,引入vue-property-decorator之后,script中的标签就变为这样
<script lang="ts">
	// 按需引入自己需要的功能模块
	import { Component, Vue } from 'vue-property-decorator'

	@Component
	export default class App extends Vue {
    	name:string = 'Simon Zhang'
	    // computed
	    get MyName():string {
	    	return `My name is ${this.name}`
	    }
		// 钩子函数
		mounted() {
    		this.sayHello();
  		}
  		// methods
  		sayHello():void {
   			alert(`Hello ${this.name}`)
  		}
	}
</script>

二、具体用法
总的来说,vue-property-decorator 提供了以下的装饰器和功能:vue-property-decorator

  • @Prop
  • @PropSync
  • @Model
  • @ModelSync
  • @Watch
  • @Provide
  • @Inject
  • @ProvideReactive
  • @InjectRactive
  • @Emit
  • @Ref
  • @VModel
  • @Component
  • Mixins

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

@Prop装饰器接收一个参数,这个参数可以有三种写法:

  • Constructor,例如String,Number,Boolean等,指定 prop 的类型;
  • Constructor[],指定prop 的可选类型;
  • PropOptions,可以使用以下选项:type,default,required,validator。

注意:属性的 ts 类型后面需要加上 undefined 类型;或者在属性名后面加上!,表示非 null 和 非 undefined 的断言,否则编译器会给出错误提示。

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

@Component
export default class MyComponent extends Vue {
    @Prop(Number) readonly propA: number | undefined
    @Prop({ default: 'default value' }) readonly propB!: string
    @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}

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

@PropSync装饰器与@prop用法类似,二者的区别在于:

  • @PropSync 装饰器接收两个参数:
    propName: string 表示父组件传递过来的属性名;
    options: Constructor | Constructor[] | PropOptions 与@Prop的第一个参数一致;
  • @PropSync 会生成一个新的计算属性。

注意:使用 PropSync 的时候是要在父组件配合 .sync 使用的

// 父组件调用子组件(PropSyncComponent)
<PropSyncComponent :like.sync="like"></PropSyncComponent>
<script lang="ts">
import { Component, Prop, Vue, PropSync } from 'vue-property-decorator';
 
@Component
export default class PropSyncComponent extends Vue {
	// 接收父组件传值(like)并生成一个新的属性(syncedlike)
    @PropSync('like', { type: String }) syncedlike!: string; // 用来实现组件的双向绑定,子组件可以更改父组件穿过来的值
 
    editLike(): void {
    	this.syncedlike = '子组件修改过后的syncedlike!'; // 双向绑定,更改 syncedlike 会更改父组件的 like
    }
}
</script>

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

@Model装饰器允许我们在一个组件上自定义v-model,接收两个参数:

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

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

// 父组件
<template>
    <div class="Model">
    	<ModelComponent v-model="fooTs" value="some value"></ModelComponent>
    	<div>父组件 app : {{fooTs}}</div>
    </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ModelComponent from '@/components/ModelComponent.vue';
 
@Component({ components: {ModelComponent} })
export default class ModelPage extends Vue {
    private fooTs = 'App Foo!';
}
</script>
// 子组件
<template>
    <div class="hello">
    	子组件:<input type="text" :value="checked" @input="inputHandle($event)"/>
    </div>
</template>
 
<script lang="ts">
import { Component, Vue, Model } from 'vue-property-decorator';
 
@Component
export default class ModelComponent extends Vue {
	@Model('change', { type: String }) readonly checked!: string
 
    public inputHandle(that: any): void {
    	this.$emit('change', that.target.value); // 后面会讲到@Emit,此处就先使用this.$emit代替
    }
}
</script>

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

@Watch 装饰器接收两个参数:

  • path: string 被侦听的属性名
  • options?: WatchOptions={} options可以包含两个属性 :
    immediate?:boolean 侦听开始之后是否立即调用该回调函数
    deep?:boolean 被侦听的对象的属性被改变时,是否调用该回调函数

侦听开始,发生在 beforeCreate 钩子之后,created 钩子之前

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

@Component
export default class MyComponent 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) {}
}

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

父组件不便于向子组件传递数据或者多层级数据传递的场景,可以把数据通过 Provide 传递下去,然后子组件通过 Inject 来获取。

两者需要一起使用。

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'
}

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

响应式的注入, 会同步更新到子组件中。两者需要一起使用。

与 Provide/Inject 比较:
相同点:Provide/ProvideReactive 提供的数据,在内部组件使用 Inject/InjectReactive 都可取到。
不同点:如果提供 (ProvideReactive) 的值被父组件修改,则子组件可以使用 InjectReactive 捕获此修改。

// 父组件
<template>
	<div id="app">
    	<input type="text" v-model="bar">
    	<InjectComponent />
    </div>
</template>
<script>
@Component({
	InjectComponent
})
export default class App extends Vue {
	@ProvideReactive(s) private bar = 'deeper lorry'
}
</script>
// 子组件
<script>
@Component
export default class InjectComponent extends Vue {
	@InjectReactive(s) private baz!: string
}
</script>

7.@Emit(event?: string)

  • 接受一个参数 event?: string, 如果没有的话会自动将 camelCase 转为 camel-case 作为事件名。
  • 会将函数的返回值作为回调函数的第二个参数, 如果是 Promise 对象,则回调函数会等 Promise resolve 掉之后触发。
  • 如果 $emit 还有别的参数, 比如点击事件的 event , 会在返回值之后, 也就是第三个参数。
// 父组件
<template>
    <EmitComponent v-on:button-click='listenChange'/>
</template>

<script lang="ts">
@Component({
    components: { EmitComponent }
})
export default class App extends Vue {
    private listenChange(value: number, event: any) {
    	console.log(value, e);
    }
}
</script>
// 子组件
<template>
    <div>
    	<button @click="emitChange">Emit!!</button>
    </div>
</template>

<script lang="ts">
@Component
export default class EmitComponent extends Vue {
    private count = 0;

    @Emit('button-click')
    private emitChange() {
    	this.count += 1;
    	return this.count;
    }
}
</script>

8.@Ref(refKey?: string)

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


<template>
    <div class="PropSync">
    	<button @click="getRef()" ref="aButton">获取ref</button>
    	<RefComponent name="names" ref="RefComponent"></RefComponent>
    </div>
</template>
 
<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator';
import RefComponent from '@/components/RefComponent.vue';
 
@Component({
    components: { RefComponent }
})
export default class RefPage extends Vue {
    @Ref('RefComponent') readonly RefC!: RefComponent;
    @Ref('aButton') readonly ref!: HTMLButtonElement;
    getRef() {
    	console.log(this.RefC);
    	console.log(this.ref);
    }
}
</script>

9.@VModel(propsArgs?: PropOptions)

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

@Component
export default class YourComponent extends Vue {
    @VModel({ type: String }) name!: string
}

10.@Component(options:ComponentOptions = {})

@Component 装饰器可以接收一个对象作为参数,可以在对象中声明 components ,filters,directives 等未提供装饰器的选项,也可以声明 computed,watch 等。

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 MyCompoent extends Vue{
   
}

11.Mixins

在使用 Vue 进行开发时我们经常要用到混合,结合 TypeScript 之后我们有两种 mixins 的方法:
方法一:vue-class-component提供的。

//定义要混合的类 mixins.ts
import Vue from 'vue';
import Component from 'vue-class-component';

@Component // 一定要用Component修饰
export default class myMixins extends Vue {
    value: string = "Hello"
}
// 引入
import Component {mixins} from 'vue-class-component';
import myMixins from 'mixins.ts';

@Component
export class myComponent extends mixins(myMixins) {
	// 直接extends myMinxins 也可以正常运行
    created(){
        console.log(this.value) // => Hello
    }
}

方法二:在@Component中混入。

// mixins.ts
import { Vue, Component } from 'vue-property-decorator';

declare module 'vue/types/vue' {
    interface Vue {
    	value: string;
    }
}
 
@Component
export default class myMixins extends Vue {
    value: string = 'Hello'
}
import { Vue, Component, Prop } from 'vue-property-decorator';
import myMixins from '@static/js/mixins';

@Component({
    mixins: [myMixins]
})
export default class myComponent extends Vue{
	created(){
    	console.log(this.value) // => Hello
    }
}

总结:两种方式不同的是在定义 mixins 时如果没有定义 vue/type/vue 模块, 那么在混入的时候就要继承该 mixins;如果定义了 vue/type/vue 模块,在混入时可以在 @Component 中 mixins 直接混入。

参考地址:https://github.com/kaorun343/vue-property-decorator#VModel


版权声明:本文为u014395524原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。