✨vue2.6 装饰器,用了就会真香🤣

8,803 阅读3分钟

背景:最近把装饰器用在了vue2.6中,发现代码量和复杂度减少了很多,我们先来看一下什么是装饰器,引用了其他人总结的一句话。

image.png

什么是装饰器

Decorator 是 ES7 的一个新语法,目前仍处于第2阶段提案中,正如其“装饰器”的叫法所表达的,他通过添加@方法名可以对一些对象进行装饰包装然后返回一个被包装过的对象,可以装饰的对象包括:类,属性,方法等。

方法日志 log

先从一个简单需求说起,比如要知道一个方法是否正在执行中、是否执行成功和是否执行异常,正常会如下这个。

改造前

methods: {
    handleSubmit() {
        const fnName = 'handleSubmit'
        console.log(`方法正在执行 ${fnName}`)
        try {
            const param = {}
            param = 'xxx'
            console.log(`方法执行成功 ${fnName}`)
        } catch (error) {
            console.log(`方法执行失败 ${fnName}${error}`)
        }
    }
}

改造后

// utils/decorator.js
/**
 * 输出方法日日志
 * @param {*} type
 */
export const log = type => {
  return (target, name, descriptor) => {
    const method = descriptor.value
    descriptor.value = (...args) => {
      console.info(`(${type}) 正在执行: ${name}(${args}) = ?`)
      let ret
      try {
        ret = method.apply(target, args)
        console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`)
      } catch (error) {
        console.error(`(${type}) 失败: ${name}(${args}) => ${error}`)
      }
      return ret
    }
  }
}
import { log } from '@utils/decorator.js'

methods: {
    @log()
    handleSubmit() {
        const param = {}
        param = 'xxx'
    }
}

运行效果

image.png

接下来开始输出一些对代码有意义的了,继续往下看。

Element ui Form 提交前校验

在使用Element ui的表单组件进行提交到后台时,通常会先验证参数,表单的具体API请步移此处查看。

改造前

// 假设 this.formEl 是 表单的$ref
methods: {
    async handleSubmit() {
        const [validateErr] = await this.formEl.validate()
        if (validateErr) return
        
        const [err] = await to(this.$api.xx())
        if (err) return this.$message.error('提交失败')
        this.$message.success('提交成功')
    }
}

改造后

// utils/decorator.js
/**
 * 表单校验
 * @param {String} formElKey - 表单el
 */
export const formValidation = (formElKey = 'formEl') => {
  return (target, name, descriptor) => {
    const method = descriptor.value
    descriptor.value = async function() {
      const _this = this._isVue ? this : target
      const isValidate = _this[formElKey]?.validate
      if (isValidate) {
        const [, res] = await to(isValidate())
        if (!res) return false
      }
      return method.apply(_this, arguments)
    }
  }
}
import { formValidation } from '@utils/decorator.js'

methods: {
    @formValidation('formEl')
    handleSubmit() {
        const [err] = await to(this.$api.xx())
        if (err) return this.$message.error('提交失败')
        this.$message.success('提交成功')
    }
}

是不是开始觉得有思路了,好像很多东西都可以用装饰器了,继续往下看,持续释放大招。

Element ui 的 异步messageBox

发现官方的异步messageBox写法,代码量很多,而我们大多数是异步方法不同,其余都基本一直,即使想变个title或者content,也可以改个参数就好了,请步移此处查看。

改造前

methods: {
    handleSave() {
        this.$confirm('确定执行批量删除用户吗?', '批量删除用户', {
            dangerouslyUseHTMLString: true,
            distinguishCancelAndClose: true,
            confirmButtonText: '删除',
            beforeClose: async (action, instance, done) => {
                if (action !== 'confirm') return done()
                instance.confirmButtonText = '执行中...'
                const [err] = await this.$to(this.$api.delUser({ ids }))
                if (err) return done()
                this.$message.success('批量删除成功!')
                done()
            }
          })
    }
}

改造后

// utils/decorator.js
/**
 * 确认框
 * @param {String} title - 标题
 * @param {String} concent - 内容
 * @param {String} confirmButtonText - 确认按钮名称
 * @returns 
 */
export const confirm = (title, concent, confirmButtonText = '确定') => {
  return (target, name, descriptor) => {
    const method = descriptor.value
    descriptor.value = function (...args) {
      const isUseFunction = (key) => toType(key, 'Function') ? key(...args) : key
      const _this = this._isVue ? this : target
      const _title = isUseFunction(title)
      const _concent = isUseFunction(concent)

      return _this.$confirm(_concent, _title, {
        dangerouslyUseHTMLString: true,
        distinguishCancelAndClose: true,
        confirmButtonText: confirmButtonText,
        beforeClose: async (action, instance, done) => {
          if (action !== 'confirm') return done()
          instance.confirmButtonText = '执行中...'
          const [err] = await to(method.call(_this, ...args), instance, 'confirmButtonLoading')
          if (err) return console.error(err)
          done()
        }
      })
    }
  }
}
import { formValidation } from '@utils/decorator.js'

methods: {
    @confirm('批量删除用户', '确定执行批量删除用户吗?', '删除')
    async handleDel(ids) {
        const [err] = await this.$to(this.$api.delUser({ ids }))
        if (err) return
        this.$message.success('批量删除成功!')
        this.getData()
    }
}

运行效果

image.png

防抖

// utils/decorator.js
/**
 * 防抖,连续操作时,只在最后一次触发
 * @export
 * @param {Function} fun - 运行函数
 * @param {Number} wait - 延迟时间
 * @returns
 */
export function debounce(wait) {
  return function(target, name, descriptor) {
    const fn = descriptor.value
    let timer = null
    descriptor.value = function() {
      const _this = this._isVue ? this : target
      clearTimeout(timer)
      timer = setTimeout(() => {
        fn.apply(_this, arguments)
      }, wait)
    }
  }
}
import { debounce } from '@utils/decorator.js'

methods: {
    @debounce(500)
    handleSubmit() {
        console.log('试试就试试')
    }
}

节流

// utils/decorator.js
/**
 * 节流,一定时间内,只能触发一次操作
 * @export
 * @param {Function} fn - 运行函数
 * @param {Number} wait - 延迟时间
 * @returns
 */
export function throttle(wait) {
  return function(target, name, descriptor) {
    const fn = descriptor.value
    let canRun = true
    descriptor.value = function() {
      const _this = this._isVue ? this : target
      if (!canRun) return
      fn.apply(_this, arguments)
      canRun = false
      setTimeout(() => {
        canRun = true
      }, wait)
    }
  }
}
import { throttle } from '@utils/decorator.js'

methods: {
    @throttle(500)
    handleSubmit() {
        console.log('试试就试试')
    }
}

缓存计算结果

/**
 * 缓存计算结果
 * @export
 * @param {Function} fn
 * @returns
 */
export function cached() {
  return function(target, name, descriptor) {
    const method = descriptor.value
    const cache = new Map()
    descriptor.value = function() {
      const _this = this._isVue ? this : target
      const key = JSON.stringify(arguments)
      if (!cache.has(key)) {
        cache.set(key, method.apply(_this, arguments))
      }
      return cache.get(key)
    }
  }
}
import { cached } from '@utils/decorator.js'

methods: {
    @cached()
    handleSubmit(a, b, c) {
        console.log('试试就试试')
        return a + b + c
    }
}

开启关闭loading

/**
 * 自动开启loading
 * @export
 * @param {string} [loadingKey='loading']
 * @returns
 */
export function autoSwitch(loadingKey = 'loading') {
  return function(target, name, descriptor) {
    const method = descriptor.value
    descriptor.value = async function() {
      const _this = this._isVue ? this : target
      _this[loadingKey] = true // 开启
      const [err, result] = await to(method.apply(_this, arguments))
      _this[loadingKey] = false // 关闭
      return err || result
    }
  }
}
import { autoSwitch } from '@utils/decorator.js'

methods: {
    @autoSwitch('loading')
    async handleSubmit() {
        try {
            const res = this.$api.xx()
            console.log(res)
        } catch (error) {
            console.log(error)
        }
        
    }
}

如果文章有错误或者有更多使用的方法,欢迎在评论区留言!