Using decorators in vue

1. What is a decorator

Decorator is a class related syntax sugar used to wrap or modify the behavior of classes or class methods. In fact, decorator is an implementation of decorator pattern in design pattern

2. Scene

Take an example
In the process of writing bug s in daily development, we often use anti shake and throttling, such as the following

class MyClass {
  follow = debounce(function() {
    console.log('Pay attention to me')
  }, 100)
}

const myClass = new MyClass()
// Multiple calls will output only once
myClass.follow()
myClass.follow()

The above is an example of anti shake. We wrap another function through the debounce function to realize the anti shake function. At this time, there is another requirement. For example, we want to print a log before and after calling the follow function. At this time, we can also develop another log function and continue to wrap the follow

/**
 * The outermost layer is anti shake, otherwise the log will be called many times
 */
class MyClass {
  follow = debounce(
    log(function() {
      console.log('Pay attention to me')
    }),
    100
  )
}

The debounce and log functions in the above code are essentially two wrapper functions. The packaging of the original function by these two functions changes the behavior of the original function. This is the principle of the decorator in js. We use the decorator to transform the above code

class MyClass {
  @debounce(100)
  @log
  follow() {
    console.log('Pay attention to me')
  }
}

The form of decorator is @ + function name. If there are parameters, parameters can be passed in the parentheses

3. In depth understanding

The decorator is still a function in essence, but the parameters of this function are fixed. The following is the code of the anti shake decorator

/**
*@param wait Delay duration
*/
function debounce(wait) {
  return function(target, name, descriptor) {
    descriptor.value = debounce(descriptor.value, wait)
  }
}
// Mode of use
class MyClass {
  @debounce(100)
  follow() {
    console.log('Pay attention to me')
  }
}

Let's analyze the code line by line

1. First, we define a debounce function with a parameter wait, which corresponds to @ debounce(100) used when calling the decorator below
2. The debounce function returns a new function, which is the core of the decorator. This function has three parameters. Next, analyze target one by one:
On whom is this class attribute function mounted? For example, the above example corresponds to MyClass Name: the name of this class attribute function corresponds to the follow ing above
Descriptor: This is the attribute descriptor we mentioned earlier. By directly using the attributes above the descriptor, you can realize the functions of attribute read-only, data rewriting and so on
3. Then the third line, descriptor value = debounce(descriptor.value, wait),
We have learned earlier that the value on the attribute descriptor corresponds to the value of this attribute, so we rewrite this attribute and wrap it with the debounce function, so that when the function calls follow, we actually call the wrapped function

Through the above three steps, we have implemented the decorator available on the class attribute and applied it to the class attribute at the same time

4. Use

Using decorators in Vue

1. Configure the base environment

Except for some old projects, we usually use the scaffold vue-cli3/4 to create a new Vue project. At this time, the new project supports the decorator by default, and there is no need to configure too many additional things. If your project uses eslint, you need to configure the following contents for eslint.

  parserOptions: {
    ecmaFeatures:{
      // Support decorator
      legacyDecorators: true
    }
  }

If you use vscode, you may encounter an error in vetur. In setting JS plus

    "vetur.validation.script": false,

2. Using the decorator

Although we usually export an object when writing Vue components, this does not affect our direct use of decorators in components. For example, take the log example in the above example.

function log() {
  /**
   * @param target The object corresponding to methods
   * @param name The name of the corresponding property method
   * @param descriptor Modifier corresponding to property method
   */
  return function(target, name, descriptor) {
    console.log(target, name, descriptor)
    const fn = descriptor.value
    descriptor.value = function(...rest) {
      console.log(`This is the calling method[ ${name}]Log printed before`)
      fn.call(this, ...rest)
      console.log(`This is the calling method[ ${name}]Post print log`)
    }
  }
}

export default {
  created() {
    this.getData()
  },
  methods: {
    @log()
    getData() {
      console.log('get data')
    }
  }
}

Looking at the code as like as two peas, is it not easy to find that decorators are used in Vue, which is exactly the same as those used on class's properties. But one thing to note is that decorations are used in the methods method, and the target of decorator is methods.

In addition to using decorators on methods, you can also use decorators on life cycle hook functions. At this time, target corresponds to the whole component object.

3. Some common decorators

1. Function throttling and anti chattering

Function throttling and anti shake are widely used. In general, the functions to be called are packaged through the throttle or debounce methods. Now you can use the above contents to encapsulate these two functions into decorators. Anti shake throttling uses the methods provided by lodash. You can also implement throttling and anti shake functions by yourself

import { throttle, debounce } from 'lodash'
/**
 * Function throttle decorator
 * @param {number} wait Throttling milliseconds
 * @param {Object} options Throttle option object
 * [options.leading=true] (boolean): Specifies that the call is made before throttling begins.
 * [options.trailing=true] (boolean): Specifies that the call occurs after throttling ends.
 */
export const throttle =  function(wait, options = {}) {
  return function(target, name, descriptor) {
    descriptor.value = throttle(descriptor.value, wait, options)
  }
}

/**
 * Function anti shake decorator
 * @param {number} wait The number of milliseconds to delay.
 * @param {Object} options Option object
 * [options.leading=false] (boolean): Specifies that a call is made before the delay begins.
 * [options.maxWait] (number): Set the maximum value that func is allowed to delay.
 * [options.trailing=true] (boolean): Specifies the call after the end of the delay.
 */
export const debounce = function(wait, options = {}) {
  return function(target, name, descriptor) {
    descriptor.value = debounce(descriptor.value, wait, options)
  }
}

After encapsulation, it is used in the component

import {debounce} from '@/decorator'

export default {
  methods:{
    @debounce(100)
    resize(){}
  }
}

2. Confirmation box

When you click the delete button, you usually need to pop up a prompt box to let the user confirm whether to delete. At this time, the conventional writing method may be like this

import { Dialog } from 'vant'

export default {
  methods: {
    deleteData() {
      Dialog.confirm({
        title: 'Tips',
        message: 'Are you sure you want to delete data? This operation cannot be rolled back.'
      }).then(() => {
        console.log('Delete here')
      })
    }
  }
}

We can put forward the process confirmed above to make a decorator, as shown in the following code

import { Dialog } from 'vant'

/**
 * Confirmation prompt box decorator
 * @param {*} message Prompt information
 * @param {*} title title
 * @param {*} cancelFn Cancel callback function
 */
export function confirm(
  message = 'Are you sure you want to delete data? This operation cannot be rolled back.',
  title = 'Tips',
  cancelFn = function() {}
) {
  return function(target, name, descriptor) {
    const originFn = descriptor.value
    descriptor.value = async function(...rest) {
      try {
        await Dialog.confirm({
          message,
          title: title
        })
        originFn.apply(this, rest)
      } catch (error) {
        cancelFn && cancelFn(error)
      }
    }
  }
}

Then when you use the confirmation box, you can use it like this

export default {
  methods: {
    // You can use the default parameters without passing parameters
    @confirm()
    deleteData() {
      console.log('Delete here')
    }
  }
}

Keywords: Javascript Front-end Vue Vue.js

Added by 990805 on Wed, 22 Dec 2021 09:59:59 +0200