Skip to content
  • @Component
  • @Prop
  • @PropSync
  • @Model
  • @Watch
  • @Emit
  • @Ref
  • mixins
  • @Provide / @Inject /@ProvideReactive / @InjectReactive

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

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

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


@Component({
  components: {
    HomeComponent,
  }
})
export default class Home extends Vue {
  private title = "HomeTitle";
}
</script>

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

@Prop装饰器接收一个参数,常常这样用

  • @Prop('String') : 指定 prop 的类型,字符串 String,Number,Boolean
  • @Prop({ default: 1, type: Number }) : 对象,指定默认值 {default:'1'}
  • @Prop([String,Number]) : 数组,指定 prop 的可选类型 [String, Boolean]
typescript

// 父组件:
<template>
  <div class="Props">
    <Child :name="name" :age="age" :sex="sex"></Child>
  </div>
</template>

<script lang="ts">
import {Component, Vue,} from 'vue-property-decorator';
import Child from './Child.vue';

@Component({
  components: {Child}, // 上边有说 @Component 可接受的参数
})
export default class PropsPage extends Vue {
  private name = 'Hs';
  private age = 18;
  private sex = 1;
}
</script>

// 子组件:
<template>
  <div>
    name: {{name}} | age: {{age}} | sex: {{sex}}
  </div>
</template>

<script lang="ts">
import {Component, Vue, Prop} from 'vue-property-decorator';

@Component
export default class Child extends Vue {
   @Prop(String) readonly name!: string | undefined;//告诉ts这里一定有值
   @Prop({ default: 20, type: Number }) private age!: number;
   @Prop([String, Number]) private sex!: string | number;
}
</script>

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

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

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

@PropSync 会生成一个新计算属性的 getter 和 setter. 注意:父组件要结合.sync来使用

typescript
// 父组件:
<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 的参数一致。
typescript
// 父组件:
<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 被侦听的对象的属性被改变时,是否调用该回调函数;
typescript
// 父组件:
<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 的回调函数的参数,会放在其返回值之后作为参数被使用。
typescript
// 父组件:
<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 装饰器接收一个可选参数,用来指向元素或子组件的引用。没有提供参数,会使用装饰器后面的属性名充当参数。

typescript
// 父组件:
<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 有两种使用方法,扩展和混合:

  • 将现有的类组件扩展为本机类继承,使用本机类继承语法对其进行扩展:
typescript
// 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:

typescript
//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";
}

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

typescript
<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装饰器进行装饰。

@Provide / @Inject 和 @ProvideReactive / @InjectReactive`

提供/注入装饰器,key 可以为 string 或者 symbol 类型,使用方式都一样

  • 相同: Provide/ProvideReactive 提供的数据,在子组件内部使用 Inject/InjectReactive 都可取到
  • 不同: ProvideReactive 的值被父组件修改,子组件可以使用 InjectReactive 捕获
typescript
// 顶层组件
<template>
  <div class="about">
    <ChildComp />
  </div>
</template>

<script lang="ts">
import { Component, Provide,Vue } from "vue-property-decorator";
import ChildComp from "./Child.vue";

@Component({
  components: { ChildComp },
})
export default class About extends Vue {
  @Provide("provide_value") private p = "from provide";
}
</script>

// 子组件
<template>
  <div>
    <h3>我是子组件</h3>
    <h3>provide/inject 跨级传参 {{provide_value}}</h3>

  </div>
</template>

<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";

@Component
export default class Child extends Vue {
  @Inject() readonly provide_value!: string;
}
</script>