添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
使用装饰器和class关键字编写Vue2.x组件

使用装饰器和class关键字编写Vue2.x组件

您好,我是 沧沧凉凉 ,是一名前端开发者,目前在 掘金 知乎 以及 个人博客 上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。


相信目前在前端行业中,大部分项目依然是使用的Vue2.x来进行编写,除了很多人不愿意跳出自己的舒适圈去学习新的东西之外,还有一个原因就是Vue2.x比Vue3.x的第三方库丰富太多,因为Vue3 Composition API的关系,导致很多支持Vue2.x的第三方库与Vue3.x都不兼容。

当然这并不是本篇文章要讲的重点,本篇文章的重点其实是 vue-property-decorator 这个装饰器库提供的装饰器。

之所以要使用装饰器的原因是因为 装饰器可以极大程度简化Vue组件中各种状态的声明 ,并且Mixins的引用会变得更加明确(但是通常不推荐在一个项目中大量使用Mixins,因为会大大降低代码的可读性。)


vue-property-decorator 一共有提供以下几种装饰器:

  1. @Prop
  2. @PropSync
  3. @Model
  4. @ModelSync
  5. @Watch
  6. @Provide
  7. @Inject
  8. @ProvideReactive
  9. @InjectReactive
  10. @Emit
  11. @Ref
  12. @VModel
  13. @Component

需要值得注意的是,装饰器并不仅仅可以用在ts上面,在js上面一样可以使用! 下面就来看一下这些装饰器的魅力吧:

1. @Component/Mixins

使用class关键字来创建组件的基本方法:

import { Component, Vue, VModel, Mixins } from "vue-property-decorator";
@Component
export default class C extends Vue {}
// Mixins 括号中引入混入的文件
@Component
export default class C extends Mixins() {}

这样就可以创建一个Vue组件,同时 如果你使用了TypeScript,那么你会得到更好的类型推断。

2. @Prop/@Emit

在本库中最常用的两个装饰器,分别对应 Prop this.$emit

2.1 @Prop

用法:

@Component
export default class YourComponent extends Vue {
  // 下面3个是官方给的用法
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined

相当于:

export default {
  props: {
    propA: {
      type: Number,
    propB: {
      default: 'default value',
    propC: {
      type: [String, Boolean],

我个人比较喜欢使用下面这种写法:

@Component
export default class YourComponent extends Vue {
  @Prop({ type: Number, required: true, default: 0 }) readonly propA:
    | number
    | undefined;

@Prop() 括号里面接各种参数。

2.2 @Emit

一般来讲,要更改Prop的值则需要通过 this.$emit("xxx" , value) 这种写法,例如:

change()




    
: void {
  this.$emit("change", 5);

而使用了装饰器后可以这样写:

@Emit()
change(): number {
  return 5;

其中return的值就是 $emit 所要传递的值, 如果 @Emit() 不带参数,则默认将函数名作为 $emit 所要触发的名称 ,如果带了参数:例如 @Emit("a") ,则相当于 this.$emit("a", 5);

同时 @Emit 装饰器会自动将camelCase(驼峰命名)命名转换为kebab-case。

3. @PropSync

因为在Vue2.x里只能拥有一个 v-model ,所以可以使用 .sync 关键字来创建多个类似于 v-model 的双向绑定。

// 父组件通过:name.sync关键字
<B :name.sync="name" />
// 子组件
@PropSync("name") syncName!: string;
@Emit("update:name")
input(): string {
  return "张三";

对于 .sync 关键字,可以看 官方的文章

4. @VModel

快速创建一个 v-model

import { Component, Vue, VModel } from "vue-property-decorator";
@Component
export default class C extends Vue {
  @VModel({ type: String }) name!: string;

等同于:

export default {
  props: {
    value: {
      type: String,
  computed: {
    name: {
      get() {
        return this.value
      set(value) {
        this.$emit('input', value)

就是实现一个 v-model 双向绑定,只要在子组件中调用 this.$emit('input', value) 就可以修改传入的值,也可以配合 @Emit 装饰器使用:

@Component({
  components: { C },
export default class B extends Vue {
  @VModel({ type: String }) name!: string;
  @Emit()
  input(): string {
    return "张三";

5. @Model/@ModelSync

5.1 @Model

从上面的 v-model 双向绑定我们可以得知, v-model 实际上是绑定了一个Prop value属性,和 $emit 的input属性,如果想将 v-model 所绑定的这两个属性更改一下名字时,我们就可以使用 @Model 装饰器:

import { Vue, Component, Model } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
  @Model('change', { type: Boolean }) readonly checked!: boolean

上面的代码等同于:

export default {
  model: {
    prop: 'checked',
    event: 'change',
  props: {
    checked: {
      type: Boolean,

至于model属性,可以参考 官方文档

5.2 @ModelSync

该装饰器是结合了 @Model @VModel 两个装饰器,将两个装饰器的功能合二为一。

import { Vue, Component, ModelSync } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
  @ModelSync('checked', 'change', { type: Boolean })
  readonly checkedValue!: boolean

上面的代码等同于:

export default {
  model: {
    prop: 'checked',
    event: 'change',
  props: {
    checked: {
      type: Boolean,
  computed: {
    checkedValue: {
      get() {
        return this.checked
      set(value) {
        this.$emit('change', value)

6. @Provide/@Inject

其实我之前是不知道有这两个装饰器的,直到我最近写Vue组件中的值传递比较费劲,突然想起Vue中有 Provide/Inject ,所以去翻阅了一下Vue中的几种值传递方式,然后又想到我目前正在写的项目虽然是使用js构建,但是使用了 vue-property-decorator 装饰器库,就去翻阅一下官方文档看是否有这两个装饰器,一翻还真有。

这里说一下 vuex 存在的副作用,以及我为啥想要使用 Provide/Inject ,其实我之前一直是一个 vuex 党,对于一个复杂状态的传递我搞不清楚就统统使用 vuex ,但是有时候 vuex 会将一些简单的逻辑复杂化,因为 vuex state 状态的修改理论上都是需要通过 Mutation (虽然可以使用对象的方式绕过 Mutation 直接修改 state 的值,就跟 Prop 传递到子组件的对象,子组件也可以修改该对象下的属性一样)。

同时 vuex 还带来一个问题,因为 vuex 中存储的状态是不会因为组件的销毁而自动销毁的,除非刷新浏览器,所以有时候会因为留下了很多状态而引起一些BUG,如果要手动清除这些状态势必又会带来额外的工作量,所以为了解决子孙之间的值传递,我就想到了 Provide/Inject

但是使用 Provide/Inject 又会引发另一个问题,就是 Provide传递的值因为可以在任何子组件甚至孙组件中通过Inject来进行调用,所以该值的源头找起来就会比较麻烦,所以一定要做好注释! 不然过段时间再看代码你根本找不到该值从哪个父层级中传递而来。

我曾经接手过一个项目,它是使用 $refs 的方式进行传值,又没有注释,半找半猜终于找到了该值的源头,过程十分艰辛。

6.1 @Provide

该装饰器可以直接声明在类中的变量上,将该属性通过 provide 传递给子组件。

例子:

@Component({
  components: { B },
export default class A extends Vue {
  @Provide()
  message = {
    content: "",

相当于:

export default {
  components: { B },
  data() {
    return {
      message: { content: "" },
  provide() {
    return {
      message: this.message,

可以看到简化了非常多的代码,非常方便,同时 @Provide() 中还可以跟一个 String 类型的参数,该参数表示了 provide() 所要传递出去的名字,一旦设置在后面的 Inject 中就需要使用设置的别名来调用:

// 父组件
@Component({
  components: { B },
export default class A extends Vue {
  // Provide a
  @Provide("a")
  message = {
    content: "",
// 子组件
export default {
  name: "B",
  components: { C },
  inject: {
    // 这里的值一定要与父组件@Provide()括号中的值对应
    message: "a",

如果不设置别名,则会默认将所装饰的变量名当做Provide的名称。

6.2 @Inject

跟上面一样,使用起来非常方便:

@Component
export default class C extends Vue {
  @Inject() readonly message!: { content: string };

如果 @Inject() 括号中不带参数,则默认将后面的变量名作为注入名称。

也可以像 @Inject("a") 这样指定对应的名称,同时后面还可以跟一个对象设置其默认值: @Inject({ from: 'optional', default: 默认值 })

7. @ProvideReactive/@InjectReactive

可以看到上面演示 @Provide/@Inject 我是使用了一个 message 对象,然后修改它的 content 属性,这是因为 Provide/Inject 传递的值不是响应式,如果你将 message 对象换成一个字符串,如下所示:

export default class A extends Vue {
  @Provide()
  message = "";

则当 message 被改变时,子组件中调用该数据的界面显示不会有任何变化,换句话说就是子组件其界面不会刷新,那么这个时候 @ProvideReactive/@InjectReactive 就可以解决这个问题。

// 父组件
export default class A extends Vue {
  @ProvideReactive()
  message = "";
// 子组件
export default class B extends Vue {
  @InjectReactive() message!: string;

你再去页面中尝试,你会发现即使更改了 message 的值,在子组件调用到的界面也会进行刷新。

8. @Watch

这个很简单,就不过多讲解了,一看就会:

// 需要监听的变量名
@Watch("data")
change(): void {
  // 所要执行的语句