前端装饰器如何封装?

目录
文章目录隐藏
  1. 1、什么是装饰器
  2. 2、装饰器怎么封装
  3. 3、装饰器能干啥

1、什么是装饰器

看个例子就懂了,正常开发是这样的:

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)
    }
  }
}

以上就是为大家分享的前端装饰器的使用,先收藏一下,如果大家实战划水时间提高了,别忘了回来点个赞哦!如果大家觉得有用,就分享给你的小伙伴吧!剩下的就是快乐的划水了,哈哈。

「点点赞赏,手留余香」

2

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 前端装饰器如何封装?

发表回复