使用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本地存储方案的资料请关注脚本之家其它相关文章!
