Pain point analysis of business code

High-frequency extraction of common business code

The following requirements are common in daily service development

  • Asynchronously processing the loading state
  • The user confirms the Confirm button
  • Function anti – shake and throttling
  • Buried point etc.

And I often think

  • These processes are not part of the business code
  • Too much coupling between this code and the business code is not conducive to modification
  • The code is highly repetitive and prone to bugs

Look at the following code

// Handle loading
 async getData () {
  this.loading = true
  try {
  $apis. GetXXXX ($apis
    this.data = await this.$apis.getXXXXX({ id:this.xxxid })
  } catch (error) {
    console.error(error)
  }finally{
     this.loading = false}}Copy the code

In real programming, finally is often omitted so that the loading state remains

So let’s forget loading for a moment and see what the function should look like

// Ignore loading
 async getData () {
 // If the data fails to be retrieved or the backend returns an error, the exception will not be assigned
 this.detailsData = await this.$apis.getXXXXX({ id:this.xxxid })
}
Copy the code

I think that’s the perfect function for me and what code doesn’t go wrong?

A: Don’t write code so there are fewer bugs when the number of lines of code goes down

So in order to keep the UI from killing me, I had to do something about loading which introduced decorator mode

See how the decorator modified the above function

// String loading is registered in vue data
/** data () { return { loading: false,` } }, */
@loading('loading')
 async getData () {
 // If the data fails to be retrieved or the backend returns an error, the exception will not be assigned
 this.detailsData = await this.$apis.getXXXXX({ id:this.xxxid })
}
Copy the code

Just add a line @loading to make the previous try Cache finally more readable

For use in projects

  1. You first need to integrate the following into your ESLint configuration via ESLint validation
ParserOptions: {parser: 'babel-eslint', ecmaFeatures: {// Support legacyDecorators: true}}Copy the code
  1. Do the processing for vscode
Add a line to.vscode/settings.json to turn off vetur validation of script. I chose to leave validation to ESLint. "Vetur.validation.script ": false,Copy the code
  1. Write a decorator file
/** * loading switch decorator * @param {String} loading variable name of the current page control switch * @param {Function} errorCb request exception callback return error general do not write * if ErrorCb = function this * @example * @loading('pageLoading',function(){that.demo = '123123'}) *  async getTable(){ * this.table = this.$apis.demo() * } * @example * @loading('pageLoading',(error,that)=>{that.demo = '123123'}) * async getTable(){ * this.table = this.$apis.demo() * } */
export function loading (loading, errorCb = Function.prototype) {
  return function (target, name, descriptor) {
    const oldFn = descriptor.value
    descriptor.value = async function (. args) {
      try {
        this[loading] = true
        await oldFn.apply(this, args)
      } catch (error) {
        // globalError is console.error will not be packaged and shaken tree
        globalError(`${name}-----start-----${error}`)
        globalLog(error)
        globalError(`${name}-----end-----${error}`)
        errorCb.call(this, error, this)}finally {
        this[loading] = false}}}}Copy the code

So the loading state of a function can be handled by adding an extra try cache finally to descriptor. Value so it can handle not only loading but also any switch variable such as disable Only the most common loading is chosen to name it

So let’s look at some code from an actual project

As you can see, this function requires 142 privileges.

And the loadingList variable is switched on and off

Just one line of business code is a huge improvement in both modification and readability

Decorator code sharing

In addition to the loading decorators mentioned above, I also used some common decorators to share with you

import { debounce as _debounce, throttle as _throttle, isString } from 'lodash-es'
import { Modal } from 'ant-design-vue'
/ * * * prompt decorator * @ param {String | Object} message need to prompt the user information or confirm the configuration of the * @ param {Function} errorFn request callback to return to this anomaly Use function to bind you to */
export function confirm (message, errorFn) {
  const defaultConf = {
    // primary ghost dashed danger link
    okType: 'danger'.maskClosable: false
  }
  return function (target, name, descriptor) {
    const oldFn = descriptor.value
    descriptor.value = function (. args) {
      Modal.confirm(Object.assign(
        defaultConf,
        isString(message) ? { title: message } : message, // if use string then create Object else use Object to assign
        {
          onOk: (a)= > oldFn.apply(this, args),
          onCancel: (a)= > {
            // No matter what
            globalWarn('The user clicked cancel:${name}`)
            if (errorFn) {
              errorFn.call(this.this}}}))}}}/** ** @param {number} wait number of milliseconds * @param {config} options * @typedef config {{* leading: Boolean, * maxWait:number, * trailing:boolean * }} */
export function debounceFn (wait = 500, options = {}) {
  return function (target, name, descriptor) {
    const oldFn = descriptor.value
    descriptor.value = _debounce(oldFn, wait, options)
  }
}

export function debounceFnStart (wait = 500) {
  return function (target, name, descriptor) {
    const oldFn = descriptor.value
    descriptor.value = _debounce(oldFn, wait, { leading: true.trailing: false}}})export function debounceFnEnd (wait = 500) {
  return function (target, name, descriptor) {
    const oldFn = descriptor.value
    descriptor.value = _debounce(oldFn, wait, { leading: false.trailing: true}}})/** * * @param {*} wait * @param {config} options * @typedef config {{ * leading:boolean, * trailing:boolean, * }} */
export function throttleFn (wait = 500, options = { trailing: true, leading: true }) {
  return function (target, name, descriptor) {
    const oldFn = descriptor.value
    descriptor.value = _throttle(oldFn, wait, options)
  }
}
Copy the code

Common function decorators for businesses that require user confirmation of state switches and anti-shake throttles

Thoughts on VUE3

Vue3 uses the hook idea, but you can’t use these decorators when written in a setup function

Changes to functions should be simpler

async function getData(id){
    data.value  = await $api.getXXX({id})
}

const {loading,runFn} = useLoading(getData)

runFn = debounce(runFN)

return {runFn}
Copy the code

Is it simpler and more intuitive? Or is there a better solution? Temporary not clear

conclusion

Thank you for your attention