ts装饰器:缓存前端频繁请求的common通用接口

我写技术文章没那么多废话,直接上代码:
1.在ApiCache核心代码:(apiCache.ts)

// 制定修饰器
export const ApiCache = (...args: any) => {
  return decorate(handleApiCache, args);
}

const decorate = (handleDescription: any, entryArgs: any) => {
  // 判断当前最后数据是否是descriptor,如果是descriptor,直接使用
  // 例如 log 这样的修饰器
  if (isDescriptor(entryArgs[entryArgs.length - 1])) {
    return handleDescription(...entryArgs);
  } else {
    // 如果不是
    // 例如 add(1) plus(20) 这样的修饰器
    return function () {
      return handleDescription(...Array.prototype.slice.call(arguments), ...entryArgs);
    };
  }
}

const isDescriptor = (descriptor: any) => {
  if (descriptor) {
    return (
      descriptor.hasOwnProperty('configurable') &&
      descriptor.hasOwnProperty('enumerable') &&
      descriptor.hasOwnProperty('writable') &&
      descriptor.hasOwnProperty('value')
    );
  }
  return false;
}

const handleApiCache = (target: any, name: any, descriptor: any, ...config: any) => {
  // 拿到函数体并保存
  const fn = descriptor.value;
  // 修改函数体
  descriptor.value = function () {
    const key = generateKey(name, arguments);
    let promise = ExpiresCache.get(key);
    let firstFlag = false;
    if (!promise) {
      // 设定promise
      promise = fn.apply(null, arguments).catch((error: any) => {
        // 在请求回来后,如果出现问题,把promise从cache中删除
        console.error(`接口请求有误,清理【${name}】缓存`);
        ExpiresCache.delete(key);
        // 返回错误
        return Promise.reject(error);
      });
      // 使用缓存,缓存过期之后再次get就会获取null,而从服务端继续请求
      firstFlag = true
      ExpiresCache.set(key, promise, config[0]);
    }
    promise.then((res: any) => {
      if (firstFlag) {
        console['clog'](0, name, arguments, res);
      } else {
        console['clog'](1, name, arguments, res);
      }
    })
    return promise;
  };
  return descriptor;
}

// 生成key值
const generateKey = (name: string, args: any) => {
  return encodeURIComponent(`${name}-${JSON.stringify(args)}`);
}

class ItemCache {
  private data: any;
  private timeout: number;
  private cacheTime: number;

  constructor(data: any, timeout: number) {
    this.data = data;
    // 设定超时时间,设定为多少秒
    this.timeout = timeout;
    // 创建对象时候的时间,大约设定为数据获得的时间
    this.cacheTime = new Date().getTime();
  }
}

class ExpiresCache {
  // 定义静态数据map来作为缓存池
  static cacheMap = new Map();

  // 数据是否超时
  static isOverTime(name: any) {
    const data = ExpiresCache.cacheMap.get(name);

    // 没有数据 一定超时
    if (!data) {
      return true;
    }

    // 获取系统当前时间戳
    const currentTime = new Date().getTime();

    // 获取当前时间与存储时间的过去的秒数
    const overTime = (currentTime - data.cacheTime) / 1000;

    // 如果过去的秒数大于当前的超时时间,也返回null让其去服务端取数据
    if (Math.abs(overTime) > data.timeout) {
      // 此代码可以没有,不会出现问题,但是如果有此代码,再次进入该方法就可以减少判断。
      ExpiresCache.cacheMap.delete(name);
      return true;
    }

    // 不超时
    return false;
  }

  // 删除 cache 中的 data
  static delete(name: string | Error) {
    return ExpiresCache.cacheMap.delete(name);
  }

  // 获取
  static get(name: string | Error) {
    const isDataOverTime = ExpiresCache.isOverTime(name);
    // 如果 数据超时,返回null,但是没有超时,返回数据,而不是 ItemCache 对象
    return isDataOverTime ? null : ExpiresCache.cacheMap.get(name).data;
  }

  // 默认存储60分钟
  static set(name: string | Error, data: any, timeout = 3600) {
    // 设置 itemCache
    const itemCache = new ItemCache(data, timeout);
    // 缓存
    ExpiresCache.cacheMap.set(name, itemCache);
  }
}

//定义一个log
const clog = (type: number, name: any, params: any, data: any) => {
  if (type == 0) {
    console.log('%c 👉【非缓存】:第一次保存【%s】接口的数据,参数为:%O,数据为:%O ', 'color:green', name, Object.keys(params).length > 0 ? { ...params } : '', data)
  } else {
    console.log('%c ✨【缓存】:调用【%s】接口的缓存数据,参数为:%O,数据为:%O ', 'color:green', name, Object.keys(params).length > 0 ? { ...params } : '', data)
  }
}
console['clog'] = clog

2.在通用接口(common.ts)里使用:

import { ApiCache } from '@/utils/apiCache'//'@/utils/apiCache'是自己放apiCache.ts的位置

const ctx = ''

class GetCommonApi {
  @ApiCache(60 * 60 * 3)//时间单位是秒,其60*60*3代表是3小时
  gradeList(params?: any) {// 年级下拉
    return https.request<any>(ctx + 'biz-common/grade-list', Method.POST, params, ContentType.json)
  }

  // @ApiCache(60) 注释之后意味着每次都要请求
  collegeList(params?: any) {// 学院列表
    return https.request<any>(ctx + 'biz-common/college-list', Method.POST, params, ContentType.json)
  }
}

export const getCommonApi = new GetCommonApi()

3.在页面中使用:

import { getCommonApi } from '@/apis/common'

const { gradeList ,collegeList} = getCommonApi;

const getGradeList = () => {
  gradeList({b:0,a:9}).then((res: any) => {
    if (res.code == '0') {
      // console.log("接口获取的数据", res.data);
    }
  })
}

const getCollegeList = () => {
  collegeList().then((res: any) => {
    if (res.code == '0') {
      // console.log("接口获取的数据", res.data);
    }
  })
}

onMounted(()=>{
  getGradeList()//项目里,只要没超过缓存时间,就不会调用新接口
  getCollegeList()//这个没加缓存,每次都要请求
})

注1:ts装饰器里用的是全局缓存,所以一刷新,所有缓存过的接口会重新请求。
注2:使用了接口缓存后,只有接口名与传递的参数相同时才会取缓存数据,当不同的参数,会重新请求接口,另外缓存

4.实际效果
(1)当相同接口名,不同参数时:各自缓存,互不干扰
在这里插入图片描述
(2)从其它页面回来:已缓存的数据,直接调用缓存数据
在这里插入图片描述

5.感谢
如果觉得写得好,记得收藏哦 ~ (O_O) ~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在TypeScript中,装饰器的执行时机取决于装饰的目标。以下是不同装饰器目标的执行时机: 1. 类装饰器:在类声明被定义时立即执行,而不是在实例化类或调用方法时执行。 2. 方法装饰器:在方法被定义时立即执行,而不是在方法被调用时执行。 3. 访问器装饰器:在访问器(getter或setter)被定义时立即执行,而不是在访问器被调用时执行。 4. 属性装饰器:在属性声明被定义时立即执行,而不是在属性被访问或赋值时执行。 注意,装饰器的执行顺序是从下往上,从右往左的。也就是说,如果一个类或方法上有多个装饰器,它们将按照从下到上、从右到左的顺序依次执行。 下面是一个示例,展示了不同类型装饰器的执行时机: ```typescript function classDecorator(constructor: Function) { console.log("Class decorator"); } function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("Method decorator"); } function accessorDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("Accessor decorator"); } function propertyDecorator(target: any, propertyKey: string) { console.log("Property decorator"); } @classDecorator class ExampleClass { @propertyDecorator property = "property"; @methodDecorator method() { console.log("Method called"); } @accessorDecorator get accessor() { return this.property; } set accessor(value: string) { this.property = value; } } const example = new ExampleClass(); example.method(); console.log(example.accessor); ``` 在上面的例子中,我们定义了一个类装饰器`classDecorator`,一个方法装饰器`methodDecorator`,一个访问器装饰器`accessorDecorator`和一个属性装饰器`propertyDecorator`。当我们创建`ExampleClass`的实例时,装饰器函数会按照指定的顺序执行。你可以运行代码查看控制台输出,以了解装饰器的执行时机。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值