vue-property-decorator各个装饰器的基本用法

2,923 阅读3分钟

关于Vue中如何使用TypeScript可以看使用vue-cli3创建TypeScript项目步骤

在Vue中使用TypeScript,其实就是使用官方维护的vue-class-component[中文文档][英文文档]装饰器的API,而vue-property-decorator[本文参考]就是在vue-class-component的基础上再封装,建议不懂得可以先看看官方文档

vue-property-decorator装饰器和Mixin函数:

  • @Component
  • @Prop
  • @PropSync
  • @Model
  • @Watch
  • @Emit
  • @Ref
  • mixins

1. @Component(options:ComponentOptions = {}) 装饰器

@Component装饰器可以创建一个Class组件,它接受一个对象作为参数[参数说明]

<script lang="ts">
import { Vue, Component } from "vue-property-decorator"; //导入Component装饰器
import HomeComponent from "@/components/HomeComponent.vue"; // 引入组件
import { NavigationGuardNext, Route } from "vue-router";

@Component({
  components: {
    HomeComponent,
  },
  beforeRouteEnter(to: Route, from: Route, next: NavigationGuardNext) {
    next((vm: any) => {
      console.log(vm); // vm就是当前组件的实例
    });
  },
  beforeRouteUpdate(to: Route, from: Route, next: NavigationGuardNext) {
    next();
  },
  beforeRouteLeave(to: Route, from: Route, next: NavigationGuardNext) {
    const answer = window.confirm(
      "Do you really want to leave? you have unsaved changes!"
    );
    if (answer) {
      next();
    } else {
      next(false);
    }
  },
})
export default class Home extends Vue {
  private title = "HomeTitle";
}
</script>

2. @Prop(options: (PropOptions | Constructor[] | Constructor) = {})装饰器

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

  • Constructor,例如String,Number,Boolean等,指定 prop 的类型;
  • Constructor[],指定 prop 的可选类型;
  • PropOptions,可以使用以下选项:type,required,default,validator。
// 父组件:
<template>
  <div>
    <PropComponent height="175" sex="boy" age="26" />
  </div>
</template>
<script lang='ts'>
import { Component, Vue } from "vue-property-decorator";
import PropComponent from "@/components/PropComponent.vue";

@Component({
  components: {
    PropComponent,
  },
})
export default class PropPage extends Vue {}
</script>
<style scoped>
</style>
// 子组件:
<template>
  <div>我是一个{{ sex }},身高有{{ height }}公分,{{ age }}岁了</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop } from "vue-property-decorator";

@Component
export default class PropComponent extends Vue {
  @Prop(String)
  //   参数类型的写法
  //   private height!: string;
  //   private height?: string;
  //   private height = "175";
  private height: string | undefined;
  @Prop({ type: String, required: true, default: "boy" })
  private sex!: string;
  @Prop([String, Number])
  private age!: string | number;
}
</script>
<style scoped>
</style>

3. @PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})装饰器

@PropSync装饰器与@prop用法差不多,区别在于:

  • @PropSync 装饰器接收两个参数:
    • propName: string 表示父组件传递过来的属性名;
    • options: Constructor | Constructor[] | PropOptions 与@Prop的参数一致; @PropSync 会生成一个新计算属性的 getter 和 setter.
      注意:父组件要结合.sync来使用
// 父组件:
<template>
  <div>
    <PropSyncComponent :title.sync="title" />
    <br />
    <button @click="onChangeTitle">父组件按钮</button>
  </div>
</template>
<script lang='ts'>
import { Component, Vue } from "vue-property-decorator";
import PropSyncComponent from "@/components/PropSyncComponent.vue";

@Component({
  components: {
    PropSyncComponent,
  },
})
export default class PropSyncPage extends Vue {
  private title = "这是父组件传给子组件的一个title";
  private onChangeTitle(): void {
    this.title = "这是一个父组件改变的title";
  }
}
</script>
<style scoped>
</style>
// 子组件: 
<template>
  <div>
    <h1>{{ syncTitle }}</h1>
    <button @click="onChangeTitle">子组件按钮</button>
  </div>
</template>
<script lang='ts'>
import { Component, Vue, PropSync } from "vue-property-decorator";

@Component
export default class PropSyncComponent extends Vue {
  @PropSync("title", { type: String }) syncTitle!: string;
  private onChangeTitle(): void {
    this.syncTitle = "这是一个子组件改变的title";
  }
}
</script>
<style scoped>
</style>

4. @Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})装饰器

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

  • event: string 事件名。
  • options: Constructor | Constructor[] | PropOptions 与@Prop的参数一致。
// 父组件:
<template>
  <div>
    <ModelComponent
      v-model="title"
      @changeInput="onChangeInput"
    />
    <div>父组件title:{{ title }}</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 title = "通过v-model实现子父组件数据双向绑定";
  private onChangeInput(evt: any) {
    console.log(evt);
    this.title = evt;
  }
}
</script>
<style scoped>
</style>
// 子组件:                                                                                                       
<template>
  <div>
    <div>子组件title:{{ title }}</div>
    <input
      style="width: 50%"
      type="text"
      :value="title"
      @input="onInputHandle($event)"
    />
  </div>
</template>
<script lang='ts'>
import { Component, Model, Vue, Emit, Prop } from "vue-property-decorator";

@Component
export default class ModelComponent extends Vue {
  //@Model第一个参数changeInput可以随便写,实际上只是规定了子组件要更新父组件值需要注册的方法
  //即@Emit第一个参数名,不同也不影响什么
  @Model("changeInput", String) readonly title!: string;
  @Emit("changeInput")
  private onChangeInput(evt: string) {
    // console.log(evt);
  }
  // 监听输入
  private onInputHandle(evt: any) {
    this.onChangeInput(evt.target.value);
  }
}
</script>
<style scoped>
</style>

5. @Watch(path: string, options: WatchOptions = {})装饰器

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

  • path: string 被侦听的属性名;
  • options?: WatchOptions={} options可以包含两个属性 :
    • immediate?:boolean 侦听开始之后是否立即调用该回调函数;
    • deep?:boolean 被侦听的对象的属性被改变时,是否调用该回调函数; 下面的代码中用了不同的方法介绍了@Watch的使用:[JS用法参考]
// 父组件: 
<template>
  <div>
    <WathcComponent :enscore="enscore" :score="score" />
    <p>语文分数:{{ score.cnscore }}</p>
    <br />
    <button @click="onChangeScore">changeScore</button>
  </div>
</template>
<script lang='ts'>
import { Component, Vue, Watch } from "vue-property-decorator";
import WathcComponent from "@/components/WatchComponent.vue";
@Component({
  components: {
    WathcComponent,
  },
})
export default class WatchPage extends Vue {
  private enscore = 80;
  private score = { cnscore: 80, name: "中文分数" };
  private onChangeScore() {
    this.enscore = 90;
    this.score.cnscore++;
    // this.score = { cnscore: 90, name: "中文分数" };
    // this.score.name = "语文分数";
  }

}
</script>
<style scoped>
</style>
// 子组件:
<template>
  <div>
    <h1>一个{{ age }}岁的学生,<br />期末考试总分是{{ total }}</h1>
    <button @click="age = age + 1">addAge</button>
  </div>
</template>
<script lang='ts'>
import { Component, Prop, Vue, Watch } from "vue-property-decorator";

@Component
export default class WatchComponent extends Vue {
  @Prop(Number)
  private enscore!: number;
  @Prop()
  private score!: any;
  private age = 17;
  private total = 0;
  //1.常用方法
  //特定:当值第一次绑定的时候,不会触发监听函数,只有值发生改变才会执行。
  @Watch("age")
  onAgeChanged(newValue: number, oldValue: number) {
    console.log("age", newValue, oldValue);
  }
  //2.立即执行(immediate)属性
  //当需要在绑定值的时候也触发函数,监听值的变化,就需要用到immediate属性。
  @Watch("enscore", { immediate: false })
  onEnscoreChanged(newValue: object, oldValue: object) {
    console.log(
      "绑定时触发enscore",
      newValue,
      oldValue,
      this.score,
      this.enscore
    );
    this.total = this.score.cnscore + this.enscore;
    console.log(
      "注意这里,当immediate为false进入页面时,没有执行监听函数",
      this.total
    );
  }
  //3.深度监听
  //当需要监听复杂数据类型(对象)的改变时,上述两个方法无法监听到对象内部属性的改变,
  //只有score中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
  @Watch("score", { deep: false }) //无法监听到变化
  //   @Watch("score.cnscore", {  deep: false })//可以监听到变化
  onscoreChanged(newValue: number, oldValue: number) {
    console.log(
      "深度监听cnscore",
      newValue,
      oldValue,
      this.score.cnscore,
      this.score.name,
      this.enscore
    );
    this.total = this.score.cnscore + this.enscore;
    console.log(
      "注意这里,当deep为false时,没有执行监听函数,total没有变化",
      this.total
    );
  }
}
</script>
<style scoped>
</style>

6. @Emit(event?: string)装饰器

@Emit 装饰器接收一个可选参数,作为事件名称。

  • 如果没有提供这个参数,@Emit会将回调函数名的camelCase转为kebab-case,作为事件名称;
  • @Emit会将回调函数的返回值作为第二个参数,如果返回值是一个Promise对象,则会在触发前达到完成状态.
  • @Emit的回调函数的参数,会放在其返回值之后作为参数被使用。
// 父组件:
<template>
  <div>
    <!-- <EmitComponent :title="title" @change-title1="onChangeTitle" /> -->
    <!-- <EmitComponent :title="title" @change-title2="onChangeTitle" /> -->
    <EmitComponent
      :title="title"
      :time="time"
      :site="site"
      @change-title="onChangeTitle"
      @change-site="onChangeSite"
      @change-time="onChangeTime"
    />
  </div>
</template>
<script lang='ts'>
import { Component, Vue } from "vue-property-decorator";
import EmitComponent from "@/components/EmitComponent.vue";
@Component({
  components: {
    EmitComponent,
  },
})
export default class EmitPage extends Vue {
  private title = "EmitTitle";
  private time = "2021年1月3日12点";
  private site = "湖南长沙";
  private onChangeTitle() {
    this.title = "通过@Emit装饰器改变父组件的值";
  }
  private onChangeTime(data: string) {
    console.log(data);
    //接受子组件传过来的参数直接赋值
    this.time = data;
  }
  private onChangeSite(data: string, evt: any) {
    console.log(data, evt);
    //接受回调函数参数赋值
    this.site = evt.target.value;
  }
}
</script>
<style scoped>
</style>
// 子组件:
<template>
  <div>
    <h1>{{ title }}</h1>
    <h1>{{ time }}</h1>
    <h1>{{ site }}</h1>
    <button @click="onChangeTitle">改变标题</button>
    <br />
    <br />
    <button @click="onChangeTime">改变时间</button>
    <br />
    <br />
    <input
      type="text"
      @input="changeSite($event)"
    />
  </div>
</template>
<script lang='ts'>
import { Component, Emit, Prop, Vue } from 'vue-property-decorator'

@Component
export default class EmitComponent extends Vue {
  @Prop(String)
  private title!: string
  @Prop(String)
  private time!: string
  @Prop(String)
  private site!: string
  //第一个参数是事件名称
  @Emit('change-title')
  private changeTitle() {
    console.log('change-title')
  }
  //如果未传事件名称,@Emit会将回调函数名changeTitle2的camelCase转为kebab-case,并将其作为事件名;
  @Emit()
  private changeTime() {
    console.log('change-time')
    const date = new Date()
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    const hour = date.getHours()
    const min = date.getMinutes()
    const second = date.getSeconds()
    return (
      String(year) + '年' + String(month) + '月' + String(day) + '日' + String(hour) + '点'
    )
  }
  @Emit()
  private changeSite(evt: any) {
    console.log('change-site')
    return 'change-site'
  }
  private onChangeTitle() {
    //这样直接改变props里面的值会报警告
    // this.title = "emit-component-title";
    this.changeTitle()
  }
  private onChangeTime() {
    this.changeTime()
  }
}
</script>
<style scoped>
</style>

7. @Ref(refKey?: string)装饰器

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

// 父组件:
<template>
  <div>
    <RefComponent ref="refcomponent" />
    <button @click="onChangeChildText">change</button>
  </div>
</template>
<script lang='ts'>
import { Component, Ref, Vue } from 'vue-property-decorator'
import RefComponent from '@/components/RefComponent.vue'
@Component({
  components: {
    RefComponent,
  },
})
export default class RefPage extends Vue {
  @Ref('refcomponent') readonly refcomponent!: RefComponent
  private onChangeChildText() {
    this.refcomponent.p.innerHTML = '通过父组件改变子组件元素值'
  }
}
</script>
<style scoped>
</style>
// 子组件:
<template>
  <div>
    <p ref="p">{{text}}</p>
  </div>
</template>
<script lang='ts'>
import { Component, Ref, Vue } from 'vue-property-decorator'

@Component
export default class RefComponent extends Vue {
  private text = '@Ref(refKey?: string)装饰器的使用'
  @Ref() readonly p!: HTMLParagraphElement
}
</script>
<style scoped>
</style>

8. mixins的使用

mixins有两种使用方法,扩展和混合:

  • 将现有的类组件扩展为本机类继承,使用本机类继承语法对其进行扩展:
// mymixins.ts
import { Vue, Component } from 'vue-property-decorator';

declare module 'vue/types/vue' {
    interface Vue {
        methodFromMixins(value: number | string): void;  // 记得声明一下,要不然会报错 Property 'methodFromMixins' does not exist on type 'App'.
    }
}

@Component
export default class MyMixins extends Vue {
    public text = 'method from mixins,';
    public methodFromMixins(value: number | string): string {
        console.log(this.text, value);
        return this.text + value;
    }
    created() {
        console.log('init data,method from mixins,');
    }
}
<template>
  <div>{{methodFromMixins('hello mixins')}}</div>
</template>
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator'
import mymixins from '@/components/mixins/mymixins'
@Component({
  mixins: [mymixins],
})
export default class MixinPage extends Vue {
  created() {
      this.methodFromMixins('hi');
    console.log('father init data,method from mixins,');
  }
}
</script>
<style scoped>
</style>
  • Vue类组件提供了mixins辅助功能,以类样式方式使用mixins。通过使用mixins帮助程序,TypeScript可以推断混合类型并在组件类型上继承它们。 声明HelloMixins和HiMixins:
//mixins.ts
import { Vue, Component } from "vue-property-decorator";

@Component // 一定要用Component修饰
export class HelloMixins extends Vue {
    public hello = 'hello mixins';
}
@Component // 一定要用Component修饰
export class HiMixins extends Vue {
    public hi = 'hi mixins';
}

在类样式组件中使用它们:

<template>
  <div>{{hello}} | {{hi}}</div>
</template>
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator'
import { HelloMixins, HiMixins } from '@/components/mixins/Mixins'
import { mixins } from 'vue-class-component'
@Component
export default class MixinPage extends mixins(HelloMixins, HiMixins) {
  created() {
    console.log(this.hello + this.hi)
  }
}
</script>
<style scoped>
</style>

注意:每个超类都必须是一个类组件。它需要继承Vue构造函数作为祖先并由@Component装饰器进行装饰。

以上就是vue-property-decorator的基本用法,觉得有帮助的点个赞^_^[github]

**内容原创,整理不易,转载请注明出处!!!谢谢合作!**