使用localForage实现带过期时间的本地存储方案
作者:pe7er
前言
在前端开发中,我们经常需要将数据存储在客户端,以减少网络请求次数,提高用户体验。localStorage
和 sessionStorage
是常用的存储方案,但它们有一些局限性,例如同步 API、只能存储字符串以及大小限制等。为了解决这些问题,本文将介绍如何使用 localForage 实现一个带过期时间的本地存储方案。
什么是 localForage?
localForage 是一个优雅的本地存储库,提供了异步的 API,支持存储多种类型的数据(如对象、数组、二进制数据等),并且在内部优先使用 IndexedDB,如果不可用则回退到 WebSQL 或 localStorage。
需求分析
我们希望实现以下功能:
- 数据存储:能够存储任意类型的数据。
- 过期时间:支持设置数据的过期时间,过期后自动清除。
- 持久化:数据在刷新或重新打开页面后仍然存在,直到过期时间到达。
实现思路
为了实现带过期时间的本地存储,我们需要在存储数据的同时,记录其过期时间。在读取数据时,先检查是否过期,若未过期则返回数据,否则删除数据并返回 null
。
代码实现
下面是具体的代码实现:
import localforage from 'localforage'; // 配置 localForage localforage.config({ name: '存储名称,请根据项目名称和需求来命名', }); // 定义一个永不过期的标志 const NEVER_EXPIRES_FLAG = -1; /** * 设置存储项 * @param k 键名 * @param v 值 * @param expired 过期时间(分钟),默认永不过期 * @returns Promise */ export const setItem = (k: string, v: any, expired: number = -1) => { const expiredKey = `${k}__expires__`; let exp = 0; if (expired === NEVER_EXPIRES_FLAG) { exp = NEVER_EXPIRES_FLAG; } else if (expired >= 0) { exp = Date.now() + 1000 * 60 * expired; } // 存储过期时间 localforage.setItem(expiredKey, exp.toString()).catch((err) => { console.error('设置过期时间失败:', err); }); // 存储实际数据 return localforage.setItem(k, v); }; /** * 获取存储项 * @param k 键名 * @returns Promise<any | null> */ export const getItem = async (k: string) => { const expiredKey = `${k}__expires__`; try { const expiredValue = await localforage.getItem<string | null>(expiredKey); if (expiredValue === null) { // 未设置过期时间,视为不存在 return null; } const expiredTime = parseInt(expiredValue, 10); if (expiredTime === NEVER_EXPIRES_FLAG) { // 永不过期 return localforage.getItem(k); } if (expiredTime > Date.now()) { // 未过期,返回数据 return localforage.getItem(k); } else { // 已过期,删除数据 removeItem(k); removeItem(expiredKey); return null; } } catch (err) { console.error('获取数据失败:', err); return null; } }; /** * 删除存储项 * @param k 键名 * @returns Promise */ export const removeItem = (k: string) => { const expiredKey = `${k}__expires__`; localforage.removeItem(expiredKey).catch((err) => { console.error('删除过期时间失败:', err); }); return localforage.removeItem(k); };
代码解析
配置 localForage
localforage.config({ name: 'bdsg-client', });
- name:为应用程序指定一个名称,便于在浏览器中区分存储。
定义永不过期的标志
const NEVER_EXPIRES_FLAG = -1;
- 使用
-1
作为永不过期的标志。
设置存储项
export const setItem = (k: string, v: any, expired: number = -1) => { const expiredKey = `${k}__expires__`; let exp = 0; if (expired === NEVER_EXPIRES_FLAG) { exp = NEVER_EXPIRES_FLAG; } else if (expired >= 0) { exp = Date.now() + 1000 * 60 * expired; } // 存储过期时间 localforage.setItem(expiredKey, exp.toString()).catch((err) => { console.error('设置过期时间失败:', err); }); // 存储实际数据 return localforage.setItem(k, v); };
- 参数说明:
k
:键名。v
:值。expired
:过期时间,单位为分钟,默认为-1
(永不过期)。
- 实现逻辑:
- 生成一个对应的过期时间键名
expiredKey
。 - 根据过期时间计算实际的过期时间戳
exp
。- 如果
expired
为-1
,则设为NEVER_EXPIRES_FLAG
。 - 如果
expired
大于等于0
,则计算未来的时间戳。
- 如果
- 使用
localforage.setItem
分别存储过期时间和实际数据。
- 生成一个对应的过期时间键名
获取存储项
export const getItem = async (k: string) => { const expiredKey = `${k}__expires__`; try { const expiredValue = await localforage.getItem<string | null>(expiredKey); if (expiredValue === null) { // 未设置过期时间,视为不存在 return null; } const expiredTime = parseInt(expiredValue, 10); if (expiredTime === NEVER_EXPIRES_FLAG) { // 永不过期 return localforage.getItem(k); } if (expiredTime > Date.now()) { // 未过期,返回数据 return localforage.getItem(k); } else { // 已过期,删除数据 removeItem(k); removeItem(expiredKey); return null; } } catch (err) { console.error('获取数据失败:', err); return null; } };
- 实现逻辑:
- 先获取对应的过期时间
expiredValue
。- 如果未设置过期时间,直接返回
null
。
- 如果未设置过期时间,直接返回
- 将过期时间字符串转换为数字
expiredTime
。 - 根据过期时间判断:
- 如果是永不过期标志,直接返回数据。
- 如果未过期(
expiredTime > Date.now()
),返回数据。 - 如果已过期,删除数据并返回
null
。
- 先获取对应的过期时间
删除存储项
export const removeItem = (k: string) => { const expiredKey = `${k}__expires__`; localforage.removeItem(expiredKey).catch((err) => { console.error('删除过期时间失败:', err); }); return localforage.removeItem(k); };
- 实现逻辑:
- 同时删除数据和对应的过期时间。
使用示例
// 存储数据,设置过期时间为 5 分钟 setItem('userInfo', { name: '张三', age: 28 }, 5); // 获取数据 getItem('userInfo').then((data) => { if (data) { console.log('用户信息:', data); } else { console.log('用户信息已过期或不存在'); } }); // 删除数据 removeItem('userInfo');
注意事项
- 异步操作:localForage 的所有方法都是异步的,返回的是 Promise,所以在获取数据时需要使用
then
或async/await
。 - 数据类型:localForage 支持存储多种数据类型,包括对象、数组、Blob 等。
- 错误处理:在实际开发中,应对可能出现的错误进行处理,以提高代码的健壮性。
多实例存储
上面的代码实例全局只使用了一个实例存储,如果希望使用多实例存储,可以进行简单的修改,下面是一个使用组合函数的方式实现多实例存储的代码。
import localforage from 'localforage'; export const useLocalforage = (options: LocalForageOptions ) => { // 配置 localForage const store = localforage.createInstance({ ...options, }); // 定义一个永不过期的标志 const NEVER_EXPIRES_FLAG = -1; /** * 设置存储项 * @param k 键名 * @param v 值 * @param expired 过期时间(分钟),默认永不过期 * @returns Promise */ const setItem = (k: string, v: any, expired: number = -1): Promise<void> => { const expiredKey = `${k}__expires__`; let exp = 0; if (expired === NEVER_EXPIRES_FLAG) { exp = NEVER_EXPIRES_FLAG; } else if (expired >= 0) { exp = Date.now() + 1000 * 60 * expired; } // 存储过期时间 store.setItem(expiredKey, exp.toString()).catch((err) => { console.error('设置过期时间失败:', err); }); // 存储实际数据 return store.setItem(k, v); }; /** * 获取存储项 * @param k 键名 * @returns Promise<T | null> */ const getItem = async <T> (k: string) : Promise<T | null> => { const expiredKey = `${k}__expires__`; try { const expiredValue = await store.getItem<string | null>(expiredKey); if (expiredValue === null) { // 未设置过期时间,视为不存在 return null; } const expiredTime = parseInt(expiredValue, 10); if (expiredTime === NEVER_EXPIRES_FLAG) { // 永不过期 return store.getItem(k) as T; } if (expiredTime > Date.now()) { // 未过期,返回数据 return store.getItem(k); } else { // 已过期,删除数据 removeItem(k); removeItem(expiredKey); return null; } } catch (err) { console.error('获取数据失败:', err); return null; } }; /** * 删除存储项 * @param k 键名 * @returns Promise */ const removeItem = (k: string) => { const expiredKey = `${k}__expires__`; store.removeItem(expiredKey).catch((err) => { console.error('删除过期时间失败:', err); }); return store.removeItem(k); }; return { getItem, setItem, } } export default useLocalforage;
使用示例
<script setup lang="ts"> import { onMounted, ref } from "vue"; import useLocalForage from "./use-local-forage"; import { USER_VISITOR_COUNT, SHOW_NAVIGATOR_BOOL } from "./storage-constants"; import Demo from './Demo.vue'; const localForageInstance = useLocalForage({ name: "test", storeName: 'storeName' }); const visitorCount = ref(0); const loadStorage = async () => { try { const data = await localForageInstance.getItem<number>(USER_VISITOR_COUNT); visitorCount.value = data || 0; } catch (error) { console.error(error); } finally { recordVisitorCount(); } }; const recordVisitorCount = () => { localForageInstance.setItem(USER_VISITOR_COUNT, visitorCount.value + 1); }; onMounted(() => { loadStorage(); }) </script> <template> <h1 v-show="visitorCount">用户访问次数{{ visitorCount }}次</h1> <Demo /> </template>
不同之处是使用const store = localforage.createInstance
来创建实例,每次使用创建的store 来进行操作,并且会根据命名来存放数据,这对于分类管理数据非常有用。
当然如果命名相同,就会存放在一个库中,但建议根据功能来区分数据。比如项目数据存放在一个库中,数据 mock 存放在另一个库中。
总结
通过以上实现,我们可以方便地使用 localForage 来存储带过期时间的数据。相比传统的 localStorage
,localForage 提供了更强大的功能和更好的性能,适用于大多数前端存储场景。
本案例的所有代码托管在:multi-localforage-demo
以上就是使用localForage实现带过期时间的本地存储方案的详细内容,更多关于localForage本地存储方案的资料请关注脚本之家其它相关文章!