vue2.6 decorator, it will be really fragrant after use

What is a decorator
Decorator is a new syntax of ES7. It is still in the second stage proposal. As expressed by the name of its "decorator", it can decorate and wrap some objects by adding @ method name, and then return a wrapped object. The objects that can be decorated include classes, properties, methods, etc.

Method log
Let's start with a simple requirement, such as whether a method is being executed, successfully executed and abnormally executed. Normally, it will be as follows.

Before transformation

methods: {
    handleSubmit() {
        const fnName = 'handleSubmit'
        console.log(`Method is executing ${fnName}`)
        try {
            const param = {}
            param = 'xxx'
            console.log(`Method executed successfully ${fnName}`)
        } catch (error) {
            console.log(`Method execution failed ${fnName},${error}`)
        }
    }
}
Copy code

After transformation

// utils/decorator.js
/**
 * Output method daily log
 * @param {*} type
 */
export const log = type => {
  return (target, name, descriptor) => {
    const method = descriptor.value
    descriptor.value = (...args) => {
      console.info(`(${type}) Executing: ${name}(${args}) = ?`)
      let ret
      try {
        ret = method.apply(target, args)
        console.info(`(${type}) success : ${name}(${args}) => ${ret}`)
      } catch (error) {
        console.error(`(${type}) fail: ${name}(${args}) => ${error}`)
      }
      return ret
    }
  }
}
Copy code
import { log } from '@utils/decorator.js'

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

Operation effect

Next, start outputting something meaningful to the code. Continue to look down.

Element ui Form verification before submission
When submitting to the background using the form component of Element ui, the parameters are usually verified first. Please step here to view the specific API of the form.

Before transformation

// Suppose this.formEl is the $ref of the form
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('Submission failed')
        this.$message.success('Submitted successfully')
    }
}
Copy code

After transformation

// utils/decorator.js
/**
 * Form verification
 * @param {String} formElKey - Form 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)
    }
  }
}
Copy code
import { formValidation } from '@utils/decorator.js'

methods: {
    @formValidation('formEl')
    handleSubmit() {
        const [err] = await to(this.$api.xx())
        if (err) return this.$message.error('Submission failed')
        this.$message.success('Submitted successfully')
    }
}
Copy code

Are you beginning to feel that you have ideas? It seems that many things can use decorators. Continue to look down and release big moves.

Asynchronous messageBox for Element ui
It is found that the official asynchronous messageBox writing method has a large amount of code, but most of our asynchronous methods are different. The rest are basically the same. Even if you want to change a title or content, you can change a parameter. Please step here to check.

Before transformation

methods: {
    handleSave() {
        this.$confirm('Are you sure to delete users in batch?', 'Batch delete user', {
            dangerouslyUseHTMLString: true,
            distinguishCancelAndClose: true,
            confirmButtonText: 'delete',
            beforeClose: async (action, instance, done) => {
                if (action !== 'confirm') return done()
                instance.confirmButtonText = 'In execution...'
                const [err] = await this.$to(this.$api.delUser({ ids }))
                if (err) return done()
                this.$message.success('Batch deletion succeeded!')
                done()
            }
          })
    }
}
Copy code

After transformation

// utils/decorator.js
/**
 * Confirmation box
 * @param {String} title - title
 * @param {String} concent - content
 * @param {String} confirmButtonText - Confirm button name
 * @returns 
 */
export const confirm = (title, concent, confirmButtonText = 'determine') => {
  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 = 'In execution...'
          const [err] = await to(method.call(_this, ...args), instance, 'confirmButtonLoading')
          if (err) return console.error(err)
          done()
        }
      })
    }
  }
}
Copy code
import { formValidation } from '@utils/decorator.js'

methods: {
    @confirm('Batch delete user', 'Are you sure to delete users in batch?', 'delete')
    async handleDel(ids) {
        const [err] = await this.$to(this.$api.delUser({ ids }))
        if (err) return
        this.$message.success('Batch deletion succeeded!')
        this.getData()
    }
}
Copy code

Operation effect

Anti shake

// utils/decorator.js
/**
 * Anti shake, continuous operation, only triggered at the last time
 * @export
 * @param {Function} fun - Run function
 * @param {Number} wait - delay time 
 * @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)
    }
  }
}
Copy code
import { debounce } from '@utils/decorator.js'

methods: {
    @debounce(500)
    handleSubmit() {
        console.log('Just try')
    }
}
Copy code

throttle

// utils/decorator.js
/**
 * Throttling can only trigger one operation within a certain time
 * @export
 * @param {Function} fn - Run function
 * @param {Number} wait - delay time 
 * @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)
    }
  }
}
Copy code
import { throttle } from '@utils/decorator.js'

methods: {
    @throttle(500)
    handleSubmit() {
        console.log('Just try')
    }
}
Copy code

Cache calculation results

/**
 * Cache calculation results
 * @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)
    }
  }
}
Copy code
import { cached } from '@utils/decorator.js'

methods: {
    @cached()
    handleSubmit(a, b, c) {
        console.log('Just try')
        return a + b + c
    }
}
Copy code

Turn on / off loading

/**
 * Automatically start 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 // open
      const [err, result] = await to(method.apply(_this, arguments))
      _this[loadingKey] = false // close
      return err || result
    }
  }
}
Copy code
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)
        }
        
    }
}
Copy code

last
If you think this article is a little helpful to you, give it a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve doubts

If you think this article is useful to you, please click star: http://github.crmeb.net/u/defu esteem it a favor!

Tags: Javascript Front-end ECMAScript

Posted on Fri, 12 Nov 2021 19:21:00 -0500 by andychamberlainuk