如何实现前端请求优化-本地缓存数据

2,307 阅读7分钟

前言🌲

前端有时候一些数据不需要经常刷新,可以做一些本地缓存;

这样减少不必要的请求,减轻服务器压力,直接从本地缓存里取。

这里前端缓存的数据放在localstorage

继上一篇实现防止重复网络,这添加一个本地缓存功能

实现思路 📖

判断接口是否有配置cache属性

  • 如果有cache属性,查看当前请求地址是否有

    • 没有数据正常发送请求
    • 本地有数据查看过期时间是否过期
      • 过期正常发送请求
      • 没有过期直接返回本地数据 Promise.resolve(data)
  • 没有cache属性

    • 正常发送请求

一、配置请求接口 📖

平时写请求接口api

1.在配置请求接口的时候添加一个cache属性

2.接受两个字段: h: 小时, m: 分钟

import { httpRequest } from '@/plugin/axios'; // axios.js 文件

// 请求接口
export function getInit(params) {
  return httpRequest({
    url: '/api/xxx/xxx',
    method: 'get',
    params,
    cache: {  //+ 在这里配置一个属性用于是否开启缓存
      m: 5,   // + 缓存 5分钟
      h: 1, // + 缓存1个小时
    }
  })
}

二、使用axios库文件进行配置 📖

axios.js 配置文件
import axios from 'axios';
import { getRequest, HttpResponse, httpError } from './cacheHttp.js'; // 这里就是我们要实现的逻辑文件

// 里面做一些请求拦截,响应拦截操作 具体查看axios文档
const service = axios.create({
    baseURL: 'xxx/xxx',
});

// 请求拦截器
service.interceptors.request.use(config => {}, error => {})

// 响应拦截器
service.interceptors.response.use(response => {
  HttpResponse(response); // + 2.响应请求回来执行
}, error => {
  httpError(); // + 3. 错误处理
})

export function http(config) { // => 这里config就是传递的请求配置参数
    return getRequest(config, service); // + 1.在这里做一些逻辑操作
}


三、实现本地缓存配置文件 📖

🌟1.先封装一下localStorage文件

localStorage.js 配置文件

/**
 * 职责: localStorage 
*/

// 存入localStorage
export function setItemLocalStorage(key: string, data: any) {
  try {
    localStorage.setItem(key, JSON.stringify(data));
  } catch (e) {
    // (谷歌, 360, 火狐, ie)本地存储满了抛出的错误
    if (e.name === 'QuotaExceededError') {
      // 本地存储满了,清空本地存储
      removeItemStorage(key);
      localStorage.setItem(key, JSON.stringify(data));
    }
  }
}

// 获取localStorage
export function getItemLocalStorage(key: string) {
  const res = localStorage.getItem(key);
  try {
    return JSON.parse(res);
  } catch (e) {
    throw new TypeError(`fail to get is ${key} localStorage, info ${e}`)
  }
}

// 删除localStorage
export function removeItemStorage(key: string) {
  localStorage.removeItem(key);
}

// 清空所有localStorage
export function clearLocalStorage() {
  localStorage.clear();
}

2.工具函数封装一下

utils.js

export const isObj = obj => Object.prototype.toString.call(obj) === '[object Object]' && Object.keys(obj).length > 0;

export const isArray = arr  => Object.prototype.toString.call(arr) === '[object Array]' && arr.length > 0;

export const CACHEKEY = 'CACHE_DATA'; // 存入本地数据的key

/**
 * @param {Object} obj 
 */
export const getformatObjVal = (obj) => {
  obj = typeof obj === 'string' ? JSON.parse(`${obj}`) : obj;
  let str = [];
  for (let p in obj) {
    if (obj.hasOwnProperty(p) && p !== '_t') {
      const item = obj[p] === null ? '' : obj[p]; // 处理null
      str.push(encodeURIComponent(p) + '=' + encodeURIComponent(item));
    }
  }
  return str.join('&');
}

export const getUrl = (config) => {
  // 暂时就在get请求、post请求的时候进行操作
  const { method = 'get', params = {}, url = '', baseURL = '' } = config;
  const urlVal: string = url.replace(baseURL, '');
  return `${urlVal}?${method === 'get' ? getformatObjVal(params) : 'post'}`;
}


3.下面我就可以开始写逻辑函数啦

cacheHttp.js 配置文件

(1)初步实现存入数据到本地localStorage

/**
 * 职责: 缓存请求数据
*/
import { getUrl, isObj, CACHEKEY } from '../utils';
import { setItemLocalStorage, getItemLocalStorage } from '../utils/localStorage';

export default class CacheRequet {
  getTime(time) {
    const { h = 0, m = 0 } = time;
    const _t = h * 3600 * 1000 + m * 60 * 1000; // 所有时间戳
    return _t + Date.now();
  }

  // 获取过期时间
  getExpirationTime(config) {
    const { cache } = config;
    if (isObj(cache)) {
      return this.getTime(cache);
    }
    if (typeof cache === 'boolean') {
      // 默认开启五分钟
      return this.getTime({ m: 5 });
    }
    return Date.now(); // 存入当前时间
  }

  setStorageData(data, config) {
    const urlKey = getUrl(config); // 获取url地址作为每一个key
    // 1.先取出所有的数据 然后在把当前的值塞入进去
    const mapData = getItemLocalStorage(CACHEKEY); // 本地所有数据
    if (!mapData) {
      const params = {};
      params[urlKey] = data;
      setItemLocalStorage(CACHEKEY, params); // 存入本地
      return;
    }

    mapData[urlKey] = data; // 赋值给当前对象
    setItemLocalStorage(CACHEKEY, mapData); // 存入本地
  }

  // 保存数据到本地
  setCacheData(response) {
    const { data, config } = response;
    const { cache } = config;
    if (!cache) return; // 不要开启缓存 直接返回

    this.setStorageData({
      data,
      expirationTime: this.getExpirationTime(config) // 过期时间
    }, config)
  }
}

const cacheRequst = new CacheRequet();
// 响应请求
export const HttpResponse = (response) => {
  cacheRequst.setCacheData(response); 
};

到这里我们就可以存入本地数据;😄

​ 配置一下接口 写一个缓存5分钟

import { httpRequest } from '@/plugin/axios'; // axios.js 文件

// 请求接口
export function getInit(params) {
  return httpRequest({
    url: '/api/xxx/xxx',
    method: 'get',
    params,
    cache: {  //+ 在这里配置一个属性用于是否开启缓存
      m: 5,   // + 缓存 5分钟
    }
  })
}

本地缓存数据图:

接下来继续实现如果获取本地数据并返回给页面。

(2)实现获取本地数据

导出接口 判断本地有没有缓存:

getCacheData方法有值就返回本地缓存值,没有本地缓存返回false

1.如果有值就直接拿去本地缓存的数据,返回一个成功的Promise.resolve

2.本地没有缓存或过期时间过期,正常发送请求

import cacheHttp from './cacheRequest/index';

/**
 * @param {Function} http 请求体
 * @param {Object} options 打印控制台信息
 */
export const getRequest = (http, options) => {
  return function (config) {
    const cacheData = cacheRequst.getCacheData(config);
    if (cacheData) {
      return Promise.resolve(cacheData); // 返回本地缓存数据
    }
    // 2.返回http方法
    return getHttp.getRequest(config); // 正常发送请求
  };
}

cacheRequest.js

发送请求前先拦截请求,获取本地缓存数据,

  • 如果本地缓存有数据并过期时间没有过期,则返回本地数据
  • 没有本地数据直接发起新的请求
/**
 * 职责: 缓存请求数据
*/
import { getUrl, isObj, CACHEKEY } from '../utils';
import { setItemLocalStorage, getItemLocalStorage } from '../utils/localStorage';

export default class CacheRequet {
  constructor() {
    this.response = Object.create(null);
    this.config = Object.create(null);
  }
  
	// ... 设置本地数据函数就省略
  
  
 // 获取本地缓存数据
  getCacheData(config) {
    const { cache } = config;
    if (!cache) return; // 不要开启缓存 直接返回
    const localData = getItemLocalStorage(CACHEKEY); //1. 获取本地所有数据
    if (!localData) return false; //2. 没有数据直接返回
    const urlKey = getUrl(config); // 获取url地址作为每一个key
   	console.log(localData); // 打印数据看看
  }
  
}



打印控制台看看本地缓存出来的数据:

接下来就可以判断过期时间和当前时间进行比较:

 // 获取本地缓存数据
  getCacheData(config) {
    const { cache } = config;
    if (!cache) return; // 不要开启缓存 直接返回
    const localData = getItemLocalStorage(CACHEKEY); //1. 获取本地所有数据
    if (!localData) return false; //2. 没有数据直接返回
    const urlKey = getUrl(config); // 获取url地址作为每一个key
    // 3.判断当前数据是否过期
    const { data = {}, expirationTime = 0 } = localData[urlKey] || {};

    if (Date.now() >= expirationTime) {
      // 时间过期
      return false;
    }

    // 4.时间没有过期,返回本地数据
    return data

  }

到这一步就实现了本地缓存功能啦🎉

4.完整缓存文件代码

cacheRequest.js

/**
 * 职责: 缓存请求数据
*/
import * as interFace from '../utils/interface';

import { getUrl, isObj, CACHEKEY } from '../utils';
import { setItemLocalStorage, getItemLocalStorage } from '../utils/localStorage';

export default class CacheRequet {
  getTime(time) {
    const { h = 0, m = 0 } = time;
    const _t = h * 3600 * 1000 + m * 60 * 1000; // 所有时间戳
    return _t + Date.now();
  }

  // 获取过期时间
  getExpirationTime(config) {
    const { cache } = config;
    if (isObj(cache)) {
      return this.getTime(cache);
    }
    if (typeof cache === 'boolean') {
      // 默认开启五分钟
      return this.getTime({ m: 5 });
    }
    return Date.now(); // 存入当前时间
  }

  setStorageData(data, config) {
    const urlKey = getUrl(config); // 获取url地址作为每一个key
    // 1.先取出所有的数据 然后在把当前的值塞入进去
    const mapData = getItemLocalStorage(CACHEKEY); // 本地所有数据
    if (!mapData) {
      const params = {};
      params[urlKey] = data;
      setItemLocalStorage(CACHEKEY, params); // 存入本地
      return;
    }

    mapData[urlKey] = data; // 赋值给当前对象
    setItemLocalStorage(CACHEKEY, mapData); // 存入本地
  }

  // 保存数据到本地
  setCacheData(response) {
    const { data, config } = response;
    const { cache } = config;
    if (!cache) return; // 不要开启缓存 直接返回

    this.setStorageData({
      data,
      expirationTime: this.getExpirationTime(config) // 过期时间
    }, config)
  }

  // 获取本地缓存数据
  getCacheData(config) {
    const { cache } = config;
    if (!cache) return; // 不要开启缓存 直接返回
    const localData = getItemLocalStorage(CACHEKEY); //1. 获取本地所有数据
    if (!localData) return false; //2. 没有数据直接返回
    const urlKey = getUrl(config); // 获取url地址作为每一个key
    // 3.判断当前数据是否过期
    const { data = {}, expirationTime = 0 } = localData[urlKey] || {};

    if (expirationTime <= Date.now()) {
      // 时间过期
      return false;
    }

    // 4.时间没有过期,返回本地数据
    return data
  }
}

导出文件index.js

import cacheHttp from './cacheRequest/index';

const cacheRequst = new cacheHttp();

/**
 * @param {Function} http 请求体
 * @param {Object} options 打印控制台信息
 */
export const getRequest = (http: any, options?) => {
  return function (config) {
    const cacheData = cacheRequst.getCacheData(config);
    if (cacheData) {
      return Promise.resolve(cacheData); //1. 返回本地缓存数据 !!!
    }
    // 本地没有缓存数据 正常发送请求
    // 2.返回http方法
    return http(config);
  };
}

// 响应请求
export const HttpResponse = (response) => {
  cacheRequst.setCacheData(response);
  return response;
};

这里我使用的axios库(按自己场景配置):

axios.js

import axios from 'axios';
import { getRequest, HttpResponse, } from './cacheHttp.js'; // 这里就是我们要实现的逻辑文件

// 里面做一些请求拦截,响应拦截操作 具体查看axios文档
const service = axios.create({
    baseURL: 'xxx/xxx',
});

// 请求拦截器
service.interceptors.request.use(config => {}, error => {})

// 响应拦截器
service.interceptors.response.use(response => {
   return  HttpResponse(response); // + 2.响应请求回来执行
})

export function http(config) { // => 这里config就是传递的请求配置参数
    return getRequest(config, service); // + 1.在这里做一些逻辑操作
}


以上就实现本地缓存啦。

注意⚠️: 如果直接配置了一些响应拦截器相关,走本地缓存获取数据不会走响应拦截器,会直接返回本地缓存的数据。 后期可以可以加入一些配置。 后期在配置项加入一个回调函数解决。

源码github

可以直接导入项目使用。如果觉得我的思路有用的话不妨点个赞😄。谢谢!