三步帶你玩轉前端裝飾器

CoolLsk發表於2021-11-02
  • 什麼是裝飾器
  • 裝飾器怎麼封裝
  • 裝飾器能幹啥

1、什麼是裝飾器

看個例子就懂了eg:
正常開發是這樣的:
    1、先定義節流方法:
        methods: {
          throttle (func, delay) {            
              var timer = null;            
              return function() {                
                  var context = this;               
                  var args = arguments;                
                  if (!timer) {                    
                      timer = setTimeout(function() {             
                          func.apply(context, args);               
                          timer = null;                    
                      }, delay);                
                  }            
              }        
          }    
        }
        2、然後執行A方法:
        methods: {
             a() {
                this.throttle(()=>{
                 //執行業務邏輯
                }, 400)
            }
        }
反正就是各種巢狀,看起來程式碼很膿腫,接下來看看【裝飾器】怎麼寫↓
//使用裝飾器過後的寫法
import { throttle} from "@/utils/decorator";

methods: {
  @throttle(400)  // 裝飾器(節流)  
  a() {
    // 執行業務邏輯
    //此時會發現點選效果跟上面寫法一樣
    console.log('執行業務')
  },
}
現在看到的寫法是不是涼快了很多,沒有多層巢狀

2、裝飾器怎麼封裝

    1、在工具檔案建立decorator.js
    // utils/decorator.js
    /**
     * 節流,一定時間內,只能觸發一次操作
     * @export
     * @param {Function} fn - 執行函式
     * @param {Number} wait - 延遲時間
     * @returns
     */
    export function throttle(wait = 2000) {
      //返回值:被傳遞給函式的物件。    
      return function(target, name, descriptor) {
        // @param target 類本身
        // @param name 裝飾的屬性(方法)名稱
        // @param descriptor 屬性(方法)的描述物件
        const fn = descriptor.value 
        let canRun = true
        descriptor.value = async function(...args) {
          //具體的裝飾器業務在這裡面編寫    
          if (!canRun) return
          await fn.apply(this, args) // 執行業務下的方法
          canRun = false
          setTimeout(() => {
            canRun = true
          }, wait)
        }
      }
    }
    2、在業務板塊裡面宣告使用
       methods: {
          @throttle(400)  // 裝飾器(節流)  
          a() {
            // 執行業務邏輯
            //此時會發現點選效果跟上面寫法一樣
            console.log('執行業務')
          },
        }
    //現在看到程式碼是不是就沒有那麼膿腫了,就一行指令

3、裝飾器能幹啥

現實開發中經常遇到節流,防抖,日誌,按鈕許可權等等一些業務執行之前的攔截操作

以下是我平時使用的一些裝飾器,希望對看到這裡的你有幫助!
// utils/decorator.js
import { Dialog } from 'vant';

/**
 * loading 開關裝飾器
 * @param {String} loading 當前頁面控制開關的變數名字
 * @param {Function} errorCb 請求異常的回撥 返回error 一般不用寫
 * 如果 errorCb 為 function 為你繫結 this  如果是箭頭函式 則第二個引數為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) {
        errorCb.call(this, error, this);
      } finally {
        this[loading] = false;
      }
    };
  };
}

/**
 * 日誌注入
 * @export
 * @param {Function} fn - 執行函式
 * @param {data} 日誌需要的引數
 * @returns
 */
export function log(data) {
  return function(target, name, descriptor) {
    const fn = descriptor.value;
    descriptor.value = async function(...args) {
      await logApi(data) // 自己的日誌介面
      await fn.apply(this, args);
    }
  }
}

// utils/decorator.js
/**
 * 節流,一定時間內,只能觸發一次操作
 * @export
 * @param {Function} fn - 執行函式
 * @param {Number} wait - 延遲時間
 * @returns
 */
export function throttle(wait= 2000) {
  return function(target, name, descriptor) {
    const fn = descriptor.value
    let canRun = true
    descriptor.value = async function(...args) {
      if (!canRun) return
      await fn.apply(this, args)
      canRun = false
      setTimeout(() => {
        canRun = true
      }, wait)
    }
  }
}
// utils/decorator.js
/**
 * 防抖,連續操作時,只在最後一次觸發
 * @export
 * @param {Function} fun - 執行函式
 * @param {Number} wait - 延遲時間
 * @returns
 */
export function debounce(wait= 2000) {
  return function(target, name, descriptor) {
    const fn = descriptor.value
    let timer = null
    descriptor.value = function(...args) {
      const _this = this._isVue ? this : target
      clearTimeout(timer)
      timer = setTimeout(() => {
        fn.apply(_this, args)
      }, wait)
    }
  }
}
/**
 * 表單校驗
 * @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)
    }
  }
}
// utils/decorator.js
/**
 * 確認框
 * @param {String} title - 標題
 * @param {String} concent - 內容
 * @param {String} confirmButtonText - 確認按鈕名稱
 * @returns
 */
export const alertDecorator = ({title = '提示', message = '請輸入彈窗內容', confirmButtonText = '我知道了'}) => {
  return (target, name, descriptor) => {
    const fn = descriptor.value;
    descriptor.value = function (...args) {
        Dialog.alert({title, message, confirmButtonText}).then(() => {
          fn.apply(this, args);
        });
    }
  }
}



/**
 * 快取計算結果
 * @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)
    }
  }
}
既然看到了這裡,先收藏一下,如果實戰划水時間提高了,別忘了回來點個贊哦
如果覺得有用,就分享給你的小夥伴吧!
接下來就是快樂的划水了 O(∩_∩)O哈哈~

相關文章