使用localForage实现带过期时间的本地存储方案

 更新时间:2024年09月11日 08:46:45   作者:pe7er  
在前端开发中,我们经常需要将数据存储在客户端,以减少网络请求次数,提高用户体验,localStorage 和 sessionStorage 是常用的存储方案,但它们有一些局限性,为了解决这些问题,本文将介绍如何使用 localForage 实现一个带过期时间的本地存储方案,需要的朋友可以参考下

前言

在前端开发中,我们经常需要将数据存储在客户端,以减少网络请求次数,提高用户体验。localStoragesessionStorage 是常用的存储方案,但它们有一些局限性,例如同步 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,所以在获取数据时需要使用 thenasync/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本地存储方案的资料请关注脚本之家其它相关文章!

相关文章

  • Jil,高效的json序列化和反序列化库

    Jil,高效的json序列化和反序列化库

    下面小编就为大家带来一篇Jil,高效的json序列化和反序列化库。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • JavaScript之通过年月获取月份的天数、日期格式化、时间、补零、Date、toLocaleString、Intl、DateTimeFormat、format(问题总结)

    JavaScript之通过年月获取月份的天数、日期格式化、时间、补零、Date、toLocaleString、Intl、

    这篇文章主要介绍了JavaScript之通过年月获取月份的天数、日期格式化、时间、补零、Date、toLocaleString、Intl、DateTimeFormat、format的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-03-03
  • layui table去掉右侧滑动条的实现方法

    layui table去掉右侧滑动条的实现方法

    今天小编就为大家分享一篇layui table去掉右侧滑动条的实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • JavaScript计算出两个数的差值

    JavaScript计算出两个数的差值

    这篇文章主要为大家详细介绍了JavaScript计算出两个数的差值,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • JS产生随机数的用法小结

    JS产生随机数的用法小结

    本文给大家分享js产生随机数的用法小结,非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧
    2016-12-12
  • TS报错:Parameter 'xxx' implicitly has an 'any' type的解决方式

    TS报错:Parameter 'xxx' implicitly has an '

    这篇文章主要给大家介绍了关于TS报错:Parameter 'xxx' implicitly has an 'any' type的解决方式,文中将产生错误的原因及解决方法都介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • 原生JS实现悬停下拉菜单

    原生JS实现悬停下拉菜单

    这篇文章主要为大家详细介绍了原生JS实现悬停下拉菜单,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • javascript 取小数点后几位几种方法总结

    javascript 取小数点后几位几种方法总结

    这篇文章主要介绍了javascript 取小数点后几位几种方法总结的相关资料,这里提供了四种方法,帮助大家整理,需要的朋友可以参考下
    2017-08-08
  • Bootstrap table 服务器端分页功能实现方法示例

    Bootstrap table 服务器端分页功能实现方法示例

    这篇文章主要介绍了Bootstrap table 服务器端分页功能实现方法,结合实例形式详细分析了Bootstrap table 服务器端后台交互与分页功能相关操作技巧,需要的朋友可以参考下
    2020-06-06
  • javascript自定义事件功能与用法实例分析

    javascript自定义事件功能与用法实例分析

    这篇文章主要介绍了javascript自定义事件功能与用法,结合实例形式较为详细的分析了javascript自定义事件的原理、功能、应用与相关注意事项,需要的朋友可以参考下
    2017-11-11

最新评论