参考 Redis 缓存接口封装 LocalStorage,在全栈项目中保持一致的缓存操作体验,降低前端同学转向全栈开发的心智负担
1. 为什么需要 Redis 风格的 LocalStorage 封装
在全栈开发中,后端通常使用 Redis 进行缓存管理,而前端则使用 LocalStorage 存储本地数据。两者的 API 接口差异较大,这给从前端转向全栈的开发者带来了额外的学习成本。
通过封装一个 Redis 风格的 LocalStorage 工具类,我们可以:
- 保持前后端缓存操作接口一致
- 提供更丰富的缓存管理功能(如过期时间、键匹配等)
- 增强代码的可维护性和可读性
- 为未来可能的后端迁移做好准备
2. 核心功能实现与代码解析
完整工具类实现
ts
export abstract class CacheUtil {
/**
* 设置缓存
* @param key 缓存键
* @param value 缓存值
* @param ttl 过期时间(单位:秒),-1 表示永不过期
*/
static set(key: string, value: any, ttl: number = -1) {
const data = { value, ttl: ttl === -1 ? ttl : Date.now() + ttl * 1000 }
localStorage.setItem(key, JSON.stringify(data))
}
/**
* 获取缓存
* @param key 缓存键
* @param defaultValue 缓存不存在或过期时的默认值
* @returns 缓存值或默认值
*/
static get<T = any>(key: string, defaultValue: T | null = null): T | null {
try {
const jsonStr = localStorage.getItem(key)
if (!jsonStr) return defaultValue
const data = JSON.parse(jsonStr)
if (data.ttl === -1 || Date.now() <= data.ttl) return data.value
localStorage.removeItem(key)
return defaultValue
} catch (error: unknown) {
localStorage.removeItem(key)
return defaultValue
}
}
/**
* 获取缓存剩余过期时间(秒)
* -1 = 永久有效
* -2 = 已过期/不存在
*/
static ttl(key: string): number {
try {
const item = localStorage.getItem(key)
if (!item) return -2
const data = JSON.parse(item)
if (data.ttl === -1) return -1
const remaining = data.ttl - Date.now()
return remaining > 0 ? Math.floor(remaining / 1000) : -2
} catch {
return -2 // 解析失败,视为无效缓存
}
}
/**
* 动态设置缓存过期时间
* @param key 缓存键
* @param ttl 过期时间(秒)
* @returns 是否设置成功
*/
static expire(key: string, ttl: number): boolean {
const value = this.get(key)
if (value === null) return false
this.set(key, value, ttl)
return true
}
/**
* 删除缓存
* @param key 缓存键
*/
static del(key: string) {
localStorage.removeItem(key)
}
/**
* 清空所有缓存
*/
static flushall() {
localStorage.clear()
}
/**
* 查找缓存键(支持通配符 *,和 Redis 用法一致)
* @param pattern 匹配规则,例如 user*、*info、*token*,默认 *
* @returns 匹配的键数组
*/
static keys(pattern: string = '*'): string[] {
const allKeys = Object.keys(localStorage)
const regex = new RegExp(pattern.replace(/\*/g, '.*'))
return allKeys.filter((key) => regex.test(key))
}
/**
* 检查缓存是否存在且未过期
* @param key 缓存键
* @returns 是否存在有效缓存
*/
static exists(key: string): boolean {
return this.get(key) !== null
}
}
核心设计要点
-
数据结构设计 :使用
{ value, ttl }结构存储缓存数据,其中ttl为过期时间戳或 -1(永不过期) -
过期时间处理:
- 设置时计算绝对过期时间戳
- 获取时检查是否过期,过期则自动清理
- 提供
ttl方法查看剩余过期时间
-
错误处理:通过 try-catch 捕获 JSON 解析异常,确保缓存操作的稳定性
-
Redis 风格 API :实现了与 Redis 相似的
set、get、del、expire、keys、exists等方法 -
通配符支持 :
keys方法支持*通配符匹配,与 Redis 用法一致
3. 完整 API 接口说明
| 方法 | 功能描述 | 参数说明 | 返回值 |
|---|---|---|---|
set(key, value, ttl) |
设置缓存 | key: 缓存键 value: 缓存值 ttl: 过期时间(秒),默认 -1 | 无 |
get(key, defaultValue) |
获取缓存 | key: 缓存键 defaultValue: 默认值,默认 null | 缓存值或默认值 |
ttl(key) |
获取剩余过期时间 | key: 缓存键 | -1: 永久有效 -2: 已过期/不存在 正数: 剩余秒数 |
expire(key, ttl) |
设置过期时间 | key: 缓存键 ttl: 过期时间(秒) | 是否设置成功 |
del(key) |
删除缓存 | key: 缓存键 | 无 |
flushall() |
清空所有缓存 | 无 | 无 |
keys(pattern) |
查找匹配的键 | pattern: 匹配规则,默认 * | 匹配的键数组 |
exists(key) |
检查缓存是否存在 | key: 缓存键 | 是否存在有效缓存 |
4. 实战使用示例
基础操作
ts
// 设置缓存,1小时过期
CacheUtil.set('USER', { id: 1, name: 'John' }, 3600)
// 获取缓存
const user = CacheUtil.get('USER')
console.log(user) // { id: 1, name: 'John' }
过期时间管理
ts
// 续期缓存,设置为2小时过期
CacheUtil.expire('USER', 7200)
// 查看剩余过期时间
const remainingTime = CacheUtil.ttl('USER')
console.log(`剩余过期时间:${remainingTime}秒`)
键管理
ts
// 通配符查找键
const userKeys = CacheUtil.keys('USER*')
const infoKeys = CacheUtil.keys('*INFO')
console.log('用户相关键:', userKeys)
console.log('信息相关键:', infoKeys)
// 检查缓存是否存在
const exists = CacheUtil.exists('USER')
console.log('USER 缓存存在:', exists)
删除操作
ts
// 删除指定缓存
CacheUtil.del('USER')
// 清空所有缓存
CacheUtil.flushall()
5. 性能考量与最佳实践
性能考量
-
存储限制:LocalStorage 通常有 5MB 左右的存储限制,避免存储过大的数据
-
读取性能:频繁读取大对象会影响性能,建议将数据合理拆分
-
过期检查 :每次
get操作都会检查过期时间,对性能影响较小但需注意 -
JSON 序列化:复杂对象的序列化/反序列化会有性能开销,建议存储结构尽量简单
最佳实践
-
命名规范 :使用统一的命名前缀(如
APP_)避免键名冲突 -
数据类型:只存储必要的数据,避免存储整个应用状态
-
过期策略:为临时数据设置合理的过期时间,避免占用存储空间
-
错误处理:虽然工具类已做了错误处理,但调用时仍需考虑异常情况
-
安全注意:不要存储敏感信息(如密码、Token)到 LocalStorage
你在项目中是如何管理本地缓存的?有哪些好用的缓存策略或工具推荐?欢迎在评论区分享你的经验和想法!