一、安装与使用
一般的,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