在 UniApp 开发中,小程序 / APP 的本地存储是高频使用的能力,但原生的uni.setStorageSync/uni.getStorageSync仅提供基础的 "存、取、删" 功能,存在两大痛点:无过期机制 (数据永久存储)、仅支持字符串(存对象 / 数组需手动转换)。本文将分享一套封装后的 Cache 缓存工具类,完美解决这些问题,同时兼容多场景使用。
一、工具类核心特性
- 自动过期清理:支持设置缓存过期时间,读取时自动校验,过期数据一键清除;
- 数据类型适配 :存对象 / 数组自动
JSON.stringify,读取时自动JSON.parse,无需手动处理; - 异常容错:完善的 try-catch 包裹,避免存储异常导致页面崩溃;
- 单例设计:全局唯一实例,无需重复实例化,调用更便捷;
- 兼容扩展:保留两套缓存逻辑(核心方法 + 兼容方法),适配不同开发习惯
二、完整代码实现
步骤 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调用所有方法:
- 页面 / 组件中使用
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>
- 请求拦截器中使用(
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)
});
});
};
- 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>
四、全局引用核心优势
- 无需重复引入 :告别每个文件写
import Cache from '@/utils/cache.js',简化代码; - 跨场景调用:页面、组件、请求拦截器、全局方法中均可直接使用;
- 状态统一:单例实例,避免多次实例化导致的过期标记管理混乱;
- 易维护 :缓存逻辑集中在
utils/cache.js,修改后全局生效。
五、最佳实践
- Key 命名规范 :统一前缀(如
user_、order_、config_),避免 key 冲突;
javascript
// 推荐
uni.$cache.set('user_token', 'xxx', 3600);
uni.$cache.set('config_theme', 'dark', 0); // 0=永不过期
- 敏感数据注意:本地缓存不存储密码、支付信息等敏感数据,仅存储 Token、用户基本信息等非敏感数据;
- 过期时间规划 :
- 临时数据(如验证码):5 分钟(300 秒);
- Token:1 小时(3600 秒);
- 用户信息:1 天(86400 秒);
- 静态配置:永不过期(0 秒);
- 定期清理 :在
App.vue的onLaunch中调用uni.$cache.clearOverdue(),避免过期缓存占用空间。
六、总结
- 该工具类基于 UniApp 原生存储封装,解决了 "无过期机制、仅支持字符串" 的核心痛点;
- 通过
main.js全局挂载后,项目中可直接用uni.$cache调用所有方法,大幅简化代码; - 适配多端场景,兼顾易用性和健壮性,是 UniApp 本地缓存的通用解决方案。
无论是小程序还是 APP 开发,这套全局缓存工具类都能显著提升开发效率,减少重复代码,同时保证缓存数据的有效性和安全性。