【UniApp 实战】封装高性能本地缓存工具类:支持过期自动清理、数据类型自动转换

在 UniApp 开发中,小程序 / APP 的本地存储是高频使用的能力,但原生的uni.setStorageSync/uni.getStorageSync仅提供基础的 "存、取、删" 功能,存在两大痛点:无过期机制 (数据永久存储)、仅支持字符串(存对象 / 数组需手动转换)。本文将分享一套封装后的 Cache 缓存工具类,完美解决这些问题,同时兼容多场景使用。

一、工具类核心特性

  1. 自动过期清理:支持设置缓存过期时间,读取时自动校验,过期数据一键清除;
  2. 数据类型适配 :存对象 / 数组自动JSON.stringify,读取时自动JSON.parse,无需手动处理;
  3. 异常容错:完善的 try-catch 包裹,避免存储异常导致页面崩溃;
  4. 单例设计:全局唯一实例,无需重复实例化,调用更便捷;
  5. 兼容扩展:保留两套缓存逻辑(核心方法 + 兼容方法),适配不同开发习惯

二、完整代码实现

步骤 1:编写缓存工具类(utils/cache.js

uni 是 UniApp 框架暴露的顶级全局对象,等价于:

  • 浏览器中的 window(全局作用域);
  • Node.js 中的 global
  • 小程序中的 wx/my(原生全局对象)。

它的核心特性是:在 UniApp 项目的任何位置(组件内、纯 JS 文件、App 启动钩子、请求拦截器)都能直接访问,不受 Vue 实例、运行端的限制

而我们把 Cache 实例挂载到 uni.$cache 上,本质是给这个 "全场景全局对象" 新增了一个属性,自然就能在任何地方调用。

javascript 复制代码
/**
 * UniApp 全局缓存工具类
 * 功能:过期自动清理、对象自动转换、全局挂载、异常捕获
 * 全局调用:uni.$cache.set() / uni.$cache.get()
 */

// 默认过期时间(单位:秒),可自定义(86400=1天,3600=1小时)
const EXPIRE = 86400; 

class Cache {
  constructor() {
    // 绑定UniApp原生存储方法(同步版,保证操作顺序)
    this.cacheSetHandler = uni.setStorageSync;
    this.cacheGetHandler = uni.getStorageSync;
    this.cacheClearHandler = uni.removeStorageSync;
    // 过期标记后缀(避免和业务key冲突)
    this.cacheExpire = '_expire_2026_01_23';
  }

  /**
   * 工具方法:获取当前秒级时间戳
   */
  time() {
    return Math.round(new Date() / 1000);
  }

  /**
   * 工具方法:日期字符串转秒级时间戳(兼容后端返回格式)
   * @param {String} expiresTime 如:2026-01-23 12:00:00
   */
  strTotime(expiresTime) {
    if (!expiresTime) return 0;
    let expireStr = expiresTime.substring(0, 19).replace(/-/g, '/');
    return Math.round(new Date(expireStr).getTime() / 1000);
  }

  /**
   * 内部方法:记录缓存过期时间
   * @param {String} key 缓存key
   * @param {Number} expire 过期时间(秒),0=永不过期
   */
  setExpireCaheTag(key, expire) {
    expire = expire ?? EXPIRE;
    if (typeof expire !== 'number') return;

    // 读取已存储的过期标记列表
    let expireList = this.cacheGetHandler(this.cacheExpire) || [];
    let newExpireList = [];
    let existKeys = [];

    // 遍历更新已有key的过期时间
    if (Array.isArray(expireList) && expireList.length) {
      newExpireList = expireList.map(item => {
        existKeys.push(item.key);
        if (item.key === key) {
          item.expire = expire === 0 ? 0 : this.time() + expire;
        }
        return item;
      });
    }

    // 新增未存在的key的过期标记
    if (!existKeys.includes(key)) {
      newExpireList.push({
        key: key,
        expire: expire === 0 ? 0 : this.time() + expire
      });
    }

    // 保存过期标记列表
    this.cacheSetHandler(this.cacheExpire, newExpireList);
  }

  /**
   * 内部方法:检查缓存是否过期
   * @param {String} key 缓存key
   * @param {Boolean} isAutoDel 是否自动删除过期缓存(默认true)
   * @returns {Boolean} true=未过期,false=已过期/不存在
   */
  getExpireCahe(key, isAutoDel = true) {
    try {
      const expireKey = key + this.cacheExpire;
      const expireTime = this.cacheGetHandler(expireKey);

      // 有过期标记,校验时间
      if (expireTime) {
        const expireSec = parseInt(expireTime);
        if (expireSec && expireSec < this.time() && !Number.isNaN(expireSec)) {
          if (isAutoDel) {
            this.cacheClearHandler(key); // 删除过期缓存
            this.cacheClearHandler(expireKey); // 删除过期标记
          }
          return false;
        }
        return true;
      }

      // 无过期标记,仅判断key是否存在
      return !!this.cacheGetHandler(key);
    } catch (e) {
      console.error('缓存过期校验失败:', e);
      return false;
    }
  }

  /**
   * 核心方法:设置缓存
   * @param {String} key 缓存key
   * @param {Any} data 缓存数据(对象/数组/基本类型)
   * @param {Number} expire 过期时间(秒),默认1天,0=永不过期
   * @returns {Boolean} true=成功,false=失败
   */
  set(key, data, expire) {
    if (!key) return false;
    try {
      // 自动转换对象/数组为JSON字符串
      const storeData = typeof data === 'object' ? JSON.stringify(data) : data;
      this.setExpireCaheTag(key, expire); // 记录过期时间
      this.cacheSetHandler(key, storeData); // 存入缓存
      return true;
    } catch (e) {
      console.error('设置缓存失败:', e);
      return false;
    }
  }

  /**
   * 核心方法:获取缓存
   * @param {String} key 缓存key
   * @param {Any} defaultValue 默认值(缓存失效时返回,支持函数)
   * @param {Number} expire 重新设置的过期时间(仅默认值生效时使用)
   * @returns {Any} 缓存数据
   */
  get(key, defaultValue = null, expire) {
    if (!key) return defaultValue;
    try {
      const isValid = this.getExpireCahe(key);
      const data = this.cacheGetHandler(key);

      // 缓存有效,返回数据(布尔默认值自动解析JSON)
      if (data && isValid) {
        return typeof defaultValue === 'boolean' ? JSON.parse(data) : data;
      }

      // 缓存失效,处理默认值
      const resValue = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
      if (resValue !== null) this.set(key, resValue, expire); // 可选:存入默认值
      return resValue;
    } catch (e) {
      console.error('获取缓存失败:', e);
      return defaultValue;
    }
  }

  /**
   * 工具方法:检查缓存是否存在(未过期)
   * @param {String} key 缓存key
   * @returns {Boolean} 是否存在且有效
   */
  has(key) {
    return this.getExpireCahe(key, false);
  }

  /**
   * 核心方法:删除指定缓存
   * @param {String} key 缓存key
   * @returns {Boolean} true=成功,false=失败
   */
  clear(key) {
    if (!key) return false;
    try {
      // 同时删除缓存和过期标记
      const expireKey = key + this.cacheExpire;
      this.cacheClearHandler(expireKey);
      this.cacheClearHandler(key);
      return true;
    } catch (e) {
      console.error('删除缓存失败:', e);
      return false;
    }
  }

  /**
   * 工具方法:清除所有过期缓存(建议App启动时调用)
   */
  clearOverdue() {
    try {
      const cacheInfo = uni.getStorageInfoSync();
      if (Array.isArray(cacheInfo.keys)) {
        cacheInfo.keys.forEach(key => {
          // 跳过过期标记本身,仅清理业务缓存
          if (key !== this.cacheExpire && !key.endsWith(this.cacheExpire)) {
            this.getExpireCahe(key);
          }
        });
      }
    } catch (e) {
      console.error('清除过期缓存失败:', e);
    }
  }

  /**
   * 兼容方法:按对象格式存储(毫秒级过期)
   * @param {Object} params {name: key, value: 数据, expires: 过期时间(毫秒)}
   */
  setItem(params) {
    const opt = Object.assign({
      name: '',
      value: '',
      expires: '',
      startTime: new Date().getTime()
    }, params);

    if (!opt.name) return;
    if (opt.expires) {
      uni.setStorageSync(opt.name, JSON.stringify(opt));
    } else {
      const value = ['[object Object]', '[object Array]'].includes(Object.prototype.toString.call(opt.value))
        ? JSON.stringify(opt.value) : opt.value;
      uni.setStorageSync(opt.name, value);
    }
  }

  /**
   * 兼容方法:按对象格式读取(自动校验过期)
   * @param {String} name 缓存key
   * @returns {Any} 缓存数据(过期返回false)
   */
  getItem(name) {
    if (!name) return null;
    let item = uni.getStorageSync(name);
    try { item = JSON.parse(item); } catch (e) {}

    if (item?.startTime) {
      const now = new Date().getTime();
      if (now - item.startTime > item.expires) {
        uni.removeStorageSync(name);
        return false;
      }
      return item.value;
    }
    return item;
  }
}

// 实例化并导出
const CacheInstance = new Cache();
export default CacheInstance;

步骤 2:全局挂载(main.js

在 UniApp 的入口文件main.js中,将 Cache 实例挂载到uni对象上,实现全局引用:

javascript 复制代码
import Vue from 'vue';
import App from './App';
// 引入缓存工具类
import Cache from '@/utils/cache.js';

// 挂载到uni全局对象(核心:全局引用)
uni.$cache = Cache;

Vue.config.productionTip = false;

App.mpType = 'app';

const app = new Vue({
  ...App
});
app.$mount();

// 可选:App启动时自动清理所有过期缓存
uni.$cache.clearOverdue();

三、全局引用使用示例

完成全局挂载后,项目中任何页面 / 组件 / JS 文件 无需重复引入,直接通过uni.$cache调用所有方法:

  1. 页面 / 组件中使用
html 复制代码
<template>
  <view class="content">
    <button @click="setCache">存储用户信息</button>
    <button @click="getCache">获取用户信息</button>
    <button @click="clearCache">删除缓存</button>
  </view>
</template>

<script>
export default {
  methods: {
    // 1. 存储缓存(全局调用)
    setCache() {
      // 存储用户信息,1天过期
      uni.$cache.set('userInfo', { name: '张三', age: 25 }, 86400);
      // 存储Token,1小时过期
      uni.$cache.set('token', 'abcd123456', 3600);
      uni.showToast({ title: '存储成功' });
    },

    // 2. 获取缓存(全局调用)
    getCache() {
      // 获取用户信息(自动解析对象,默认值{})
      const userInfo = uni.$cache.get('userInfo', true);
      // 获取Token(默认值空字符串)
      const token = uni.$cache.get('token', '');
      console.log('用户信息:', userInfo);
      console.log('Token:', token);
    },

    // 3. 删除缓存(全局调用)
    clearCache() {
      uni.$cache.clear('token');
      uni.showToast({ title: 'Token已删除' });
    },

    // 4. 检查缓存是否有效
    checkCache() {
      const hasToken = uni.$cache.has('token');
      if (hasToken) {
        console.log('Token有效,可发起请求');
      } else {
        console.log('Token过期,需重新登录');
      }
    }
  }
};
</script>
  1. 请求拦截器中使用(utils/request.js

在接口请求拦截器中,全局调用缓存获取 Token,无需引入:

javascript 复制代码
// 请求拦截器
const requestInterceptor = (config) => {
  // 全局获取Token(无需import,直接调用)
  const token = uni.$cache.get('token', '');
  if (token) {
    config.header.Authorization = `Bearer ${token}`;
  }
  return config;
};

// 响应拦截器(Token过期时清除缓存)
const responseInterceptor = (response) => {
  if (response.data.code === 401) { // Token过期
    uni.$cache.clear('token'); // 全局清除Token
    uni.$cache.clear('userInfo'); // 全局清除用户信息
    uni.navigateTo({ url: '/pages/login/index' });
  }
  return response;
};

// 封装请求方法
export const request = (options) => {
  return new Promise((resolve, reject) => {
    uni.request({
      ...options,
      header: {
        'Content-Type': 'application/json'
      },
      success: (res) => resolve(responseInterceptor(res)),
      fail: (err) => reject(err)
    });
  });
};
  1. App 启动时清理过期缓存(App.vue
javascript 复制代码
<script>
export default {
  onLaunch: function() {
    console.log('App启动');
    // 全局调用:清理所有过期缓存,释放存储空间
    uni.$cache.clearOverdue();
  },
  onShow: function() {
    console.log('App显示');
  },
  onHide: function() {
    console.log('App隐藏');
  }
};
</script>

四、全局引用核心优势

  1. 无需重复引入 :告别每个文件写import Cache from '@/utils/cache.js',简化代码;
  2. 跨场景调用:页面、组件、请求拦截器、全局方法中均可直接使用;
  3. 状态统一:单例实例,避免多次实例化导致的过期标记管理混乱;
  4. 易维护 :缓存逻辑集中在utils/cache.js,修改后全局生效。

五、最佳实践

  1. Key 命名规范 :统一前缀(如user_order_config_),避免 key 冲突;
javascript 复制代码
// 推荐
uni.$cache.set('user_token', 'xxx', 3600);
uni.$cache.set('config_theme', 'dark', 0); // 0=永不过期
  1. 敏感数据注意:本地缓存不存储密码、支付信息等敏感数据,仅存储 Token、用户基本信息等非敏感数据;
  2. 过期时间规划
    • 临时数据(如验证码):5 分钟(300 秒);
    • Token:1 小时(3600 秒);
    • 用户信息:1 天(86400 秒);
    • 静态配置:永不过期(0 秒);
  3. 定期清理 :在App.vueonLaunch中调用uni.$cache.clearOverdue(),避免过期缓存占用空间。

六、总结

  1. 该工具类基于 UniApp 原生存储封装,解决了 "无过期机制、仅支持字符串" 的核心痛点;
  2. 通过main.js全局挂载后,项目中可直接用uni.$cache调用所有方法,大幅简化代码;
  3. 适配多端场景,兼顾易用性和健壮性,是 UniApp 本地缓存的通用解决方案。

无论是小程序还是 APP 开发,这套全局缓存工具类都能显著提升开发效率,减少重复代码,同时保证缓存数据的有效性和安全性。

相关推荐
游戏开发爱好者82 小时前
在 Linux 环境通过命令行上传 IPA 到 App Store,iOS自动化构建与发布
android·linux·ios·小程序·uni-app·自动化·iphone
工业甲酰苯胺2 小时前
C#中的多级缓存架构设计与实现深度解析
缓存·c#·wpf
wWYy.2 小时前
详解redis(9):数据结构set
数据库·redis·缓存
努力成为包租婆3 小时前
uniapp--原生插件开发
java·数据库·uni-app
滴水未满16 小时前
uniapp的调试和安装
uni-app
2501_9159090618 小时前
设置了 SSL Pinning 与双向 TLS 验证要怎么抓包
网络·网络协议·ios·小程序·uni-app·iphone·ssl
菜鸟小九18 小时前
redis高级(存储能力问题)
数据库·redis·缓存
壹号机长19 小时前
vue3+uniapp 今天及未来六天日期的时间段预约选择,时间段预约当前时间之前禁用选择
uni-app
yuankunliu21 小时前
【redis】4、Redis的过期策略和淘汰策略
数据库·redis·缓存