聊聊在Vue项目中使用Decorator装饰器

3,389 阅读3分钟

前言

初衷: 前几天我在公司其它Vue项目中,发现了是用Decorator装饰器模式开发的,看起来整体代码还不错,于是就做了一下笔记分享给大家,不喜勿喷。

适合人群: 初级前端开发,大佬绕道。

本项目是使用jsDecorator装饰器搭建,如果大家项目用的是ts,那么使用装饰器方法跟本文介绍的不同,请自行参考ts 使用装饰器。需要注意,使用这种模式,变量会存在污染,所以不能出现重名变量

什么是Decorator

Decorator装饰器是一种类class相关的语法,所以Decorator装饰器只能用在class类里面,在普通的语法或表达式上不能使用。个人理解:装饰器可以给我们提供一层拦截的作用,先执行装饰器里面的东西,后执行我们的操作。具体请看阮一峰老师的《装饰器》

安装

组件

npm install --save vue-class-component
npm install --save vue-property-decorator

配置

在项目的根目录babel.config.js进行配置如下

module.exports = {
  presets: [
    '@vue/app'
  ],
  plugins: [
    ['@babel/plugin-proposal-decorators', { legacy: true }],
    ['@babel/plugin-proposal-class-properties', { loose: true }],
  ]
}

在项目的根目录jsconfig.json进行配置如下

{
    "compilerOptions": {
      "experimentalDecorators": true
    }
}

使用方法

我这里就介绍一下在Vue中常用的几个方法,具体详细的可以看这里Vue-property-decorator

生命周期、methods、data

这些写法都跟原来一样,直接写就行,看如下案例对比

原写法

<script>
export default {
    data() {
        return {
            msg: "hello 蛙人"
        }
    },
    created() {
    
    },
    methods: {
        test() {
        
        }
    }
}
</script>

装饰器写法

<script>
import { Vue } from 'vue-property-decorator'
class App extends Vue {
    msg = "hello 蛙人"
    created() {
    
    }
    test() {
    
    }
}
export default App
</script>

Emit

原写法

<script>
export default {
  methods: {
    send() {
      this.$emit("custom", 123)
    }
  }
}
</script>

装饰器写法

<script>
import { Vue, Emit } from 'vue-property-decorator'
class Hello extends Vue {
  created() {
    this.send()
  }

  @Emit("custom")
  send() {
    return 123
  }
}
export default Hello
</script>

Provide

原写法

<script>
export default {
  provide() {
    return {
      msg: this.msg
    }
  }
}
</script>

装饰器写法

<script>
class App extends Vue {
  @Provide() msg = this.msg
  msg = "hello 蛙人"
}
export default App
</script>

Inject

原写法

export default {
  inject: {
    msg: {
      default: () => "",
      required: true
    } 
  }
}
</script>

装饰器写法

import { Vue, Component,Inject } from 'vue-property-decorator'
@Component
class Hello extends Vue {
  @Inject({ required: true, default: () => "" }) msg
}
export default Hello

Prop

原写法

<script>
export default {
  props: {
    msg: {
      type: () => String,
      required: true
    }
  }
}
</script>

装饰器写法

<script>
import { Vue, Prop } from 'vue-property-decorator'
class Hello extends Vue {
  @Prop({ required: true, type: String }) msg
}
export default Hello
</script>

PropSync

原写法

// 父组件
<HelloWorld :msg.sync="msg" v-show="msg"/>

// 子组件
<script>
export default {
  props: {
    msg: {
      require: true
    }
  },
  created() {
    setTimeout(() => {
      this.test()
    }, 5000)
  },
  methods: {
    test() {
      this.$emit("update:msg", false)
    }
  }
}
</script>

装饰器写法

@PropSync第一个参数则是,this.$emit("update:msg")里面的msg, 语句后面则跟着变量

<script>
import { Vue, Component, PropSync } from 'vue-property-decorator'
@Component
class Hello extends Vue {
  @PropSync("msg", { required: true }) variable
  created() {
    setTimeout(() => {
      this.variable = false
    }, 5000)
  }
}
export default Hello
</script>

Watch

原写法

export default {
  data() {
    return {
      str: 123  
    }
  },
  created() {
    setTimeout(() => {
      this.str = 12
    }, 5000)
  },
  watch: {
    str: function(newVal, oldVal) {
      console.log(newVal, oldVal)
    }
  }
}
</script>

装饰器写法

import { Vue, Component, Watch } from 'vue-property-decorator'
@Component
class Hello extends Vue {
  str = 123

  created() {
    setTimeout(() => {
      this.str = 12
    }, 2000)
  }

  @Watch("str", {deep: true})
  test(newVal, oldVal) {
    console.log(newVal, oldVal)
  }
}
export default Hello

Computed

原写法

<script>
export default {
  computed: {
    test: {
       get() {
         return this.msg
       },
       set(val) {
         return this.msg = val
       }
     }
   }
}
</script>

装饰器写法

<script>
import { Vue, Component } from 'vue-property-decorator'
@Component
class App extends Vue {
  get test() {
    return this.msg
  }
  set test(val) {
    return this.msg = val
  }

}
export default App

Model

有时候我们想给组件写一个v-model方法就可以这样,如下

原写法

// 父组件
<HelloWorld :msg="msg" v-model="msg"/>

// 子组件
<input type="text" @input="test" :value="msg">

<script>
export default {
  props: {
    msg: {
      require: true
    }
  },
  model: {
    prop: "msg",
    event: "input"
  },
  methods: {
    test(e) {
      this.$emit("input", e.target.value)
    }
  }
}
</script>

装饰器写法

// 父组件
<HelloWorld :msg="msg" v-model="msg"/>

// 子组件
<input type="text" @input="test" :value="msg">

<script>
import { Vue, Component, Model, Emit } from 'vue-property-decorator'
@Component
class Hello extends Vue {
  @Model("input", {default: () => ""}) msg
  
  test(e) {
    this.send(e)
  }
  @Emit("input")
  send(e) {
    return e.target.value
  }
}
export default Hello
</script>

Ref

原写法

<HelloWorld :msg="msg" ref="val"/>

<script>
export default {
  name: 'App',
  components: {
    HelloWorld
  },
  data() {
    return {
      msg: "hello 蛙人"
    }
  },
  mounted() {
    console.log(this.$refs.val)
  },
}
</script>

装饰器写法

<HelloWorld :msg="msg" ref="val"/>

<script>
import { Vue, Component, Ref } from 'vue-property-decorator'
@Component({
  components: {
    HelloWorld
  }
})
class App extends Vue {
  @Ref("val") val
  msg = "hello 蛙人"
  
  mounted() {
    console.log(this.val)
  }
}
export default App
</script>

Component

该方法是从组件库中导入,如果使用使用生命周期方法,记得引入此装饰器,否则不生效。该装饰器接收一个对象里面也可以写原生Vue方法。

<script>
import { Vue, Component } from 'vue-property-decorator'
@Component({
    components: {
        
    },
    watch: {
        str: function(val) {
            console.log(val)
        }
    }
})
export class App extends Vue {}
</script>

扩展

当然了可以根据自己的需求扩展封装Decorator装饰器,装饰器接收三个参数

  • 目标对象
  • 目标key
  • 描述对象

这里不明白描述对象属性可以看我这篇文章《深入理解JavaScript对象》

<script>
function Decorator(data) {
    return (vue, key, describe) => {
      // vue 当前执行环境对象
      // key 当前装饰器函数对象 test
      // describe 描述对象里面value是函数
      let fn = describe.value
      describe.value = function () {
        let status = window.confirm(data)
        if (status) return fn()
      }
    }
}
import { Vue, Component } from 'vue-property-decorator'
@Component
class App extends Vue {
  @Decorator("请点击确定")
  test() { 
    window.confirm("你是否完成了")
  }
}
export default App
</script>

上面example中,可扩展自己的Decorator装饰器,装饰器就相当于起一层拦截作用,先执行装饰器里面的操作,在执行我们函数本身的逻辑操作。我这里只做一个案例哈,具体看你们的需求。

感谢

谢谢各位在百忙之中点开这篇文章,希望对你们能有所帮助,如有问题欢迎各位大佬指正。

我是蛙人,如果觉得写得可以的话,请点个赞吧。

感兴趣的小伙伴可以加入 [ 前端娱乐圈交流群 ] 欢迎大家一起来交流讨论

往期好文

《聊聊什么是CommonJs和Es Module及它们的区别》

《带你轻松理解数据结构之Map》

《这些工作中用到的JavaScript小技巧你都知道吗?》

《【建议收藏】分享一些工作中常用的Git命令及特殊问题场景怎么解决》

《解构:使数据访问更便捷!》

《你真的了解ES6中的函数特性么?》