使用localForage實現(xiàn)帶過期時間的本地存儲方案
前言
在前端開發(fā)中,我們經(jīng)常需要將數(shù)據(jù)存儲在客戶端,以減少網(wǎng)絡(luò)請求次數(shù),提高用戶體驗。localStorage
和 sessionStorage
是常用的存儲方案,但它們有一些局限性,例如同步 API、只能存儲字符串以及大小限制等。為了解決這些問題,本文將介紹如何使用 localForage 實現(xiàn)一個帶過期時間的本地存儲方案。
什么是 localForage?
localForage 是一個優(yōu)雅的本地存儲庫,提供了異步的 API,支持存儲多種類型的數(shù)據(jù)(如對象、數(shù)組、二進制數(shù)據(jù)等),并且在內(nèi)部優(yōu)先使用 IndexedDB,如果不可用則回退到 WebSQL 或 localStorage。
需求分析
我們希望實現(xiàn)以下功能:
- 數(shù)據(jù)存儲:能夠存儲任意類型的數(shù)據(jù)。
- 過期時間:支持設(shè)置數(shù)據(jù)的過期時間,過期后自動清除。
- 持久化:數(shù)據(jù)在刷新或重新打開頁面后仍然存在,直到過期時間到達。
實現(xiàn)思路
為了實現(xiàn)帶過期時間的本地存儲,我們需要在存儲數(shù)據(jù)的同時,記錄其過期時間。在讀取數(shù)據(jù)時,先檢查是否過期,若未過期則返回數(shù)據(jù),否則刪除數(shù)據(jù)并返回 null
。
代碼實現(xiàn)
下面是具體的代碼實現(xiàn):
import localforage from 'localforage'; // 配置 localForage localforage.config({ name: '存儲名稱,請根據(jù)項目名稱和需求來命名', }); // 定義一個永不過期的標志 const NEVER_EXPIRES_FLAG = -1; /** * 設(shè)置存儲項 * @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('設(shè)置過期時間失敗:', err); }); // 存儲實際數(shù)據(jù) 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) { // 未設(shè)置過期時間,視為不存在 return null; } const expiredTime = parseInt(expiredValue, 10); if (expiredTime === NEVER_EXPIRES_FLAG) { // 永不過期 return localforage.getItem(k); } if (expiredTime > Date.now()) { // 未過期,返回數(shù)據(jù) return localforage.getItem(k); } else { // 已過期,刪除數(shù)據(jù) removeItem(k); removeItem(expiredKey); return null; } } catch (err) { console.error('獲取數(shù)據(jù)失敗:', 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:為應(yīng)用程序指定一個名稱,便于在瀏覽器中區(qū)分存儲。
定義永不過期的標志
const NEVER_EXPIRES_FLAG = -1;
- 使用
-1
作為永不過期的標志。
設(shè)置存儲項
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('設(shè)置過期時間失敗:', err); }); // 存儲實際數(shù)據(jù) return localforage.setItem(k, v); };
- 參數(shù)說明:
k
:鍵名。v
:值。expired
:過期時間,單位為分鐘,默認為-1
(永不過期)。
- 實現(xiàn)邏輯:
- 生成一個對應(yīng)的過期時間鍵名
expiredKey
。 - 根據(jù)過期時間計算實際的過期時間戳
exp
。- 如果
expired
為-1
,則設(shè)為NEVER_EXPIRES_FLAG
。 - 如果
expired
大于等于0
,則計算未來的時間戳。
- 如果
- 使用
localforage.setItem
分別存儲過期時間和實際數(shù)據(jù)。
- 生成一個對應(yīng)的過期時間鍵名
獲取存儲項
export const getItem = async (k: string) => { const expiredKey = `${k}__expires__`; try { const expiredValue = await localforage.getItem<string | null>(expiredKey); if (expiredValue === null) { // 未設(shè)置過期時間,視為不存在 return null; } const expiredTime = parseInt(expiredValue, 10); if (expiredTime === NEVER_EXPIRES_FLAG) { // 永不過期 return localforage.getItem(k); } if (expiredTime > Date.now()) { // 未過期,返回數(shù)據(jù) return localforage.getItem(k); } else { // 已過期,刪除數(shù)據(jù) removeItem(k); removeItem(expiredKey); return null; } } catch (err) { console.error('獲取數(shù)據(jù)失敗:', err); return null; } };
- 實現(xiàn)邏輯:
- 先獲取對應(yīng)的過期時間
expiredValue
。- 如果未設(shè)置過期時間,直接返回
null
。
- 如果未設(shè)置過期時間,直接返回
- 將過期時間字符串轉(zhuǎn)換為數(shù)字
expiredTime
。 - 根據(jù)過期時間判斷:
- 如果是永不過期標志,直接返回數(shù)據(jù)。
- 如果未過期(
expiredTime > Date.now()
),返回數(shù)據(jù)。 - 如果已過期,刪除數(shù)據(jù)并返回
null
。
- 先獲取對應(yīng)的過期時間
刪除存儲項
export const removeItem = (k: string) => { const expiredKey = `${k}__expires__`; localforage.removeItem(expiredKey).catch((err) => { console.error('刪除過期時間失敗:', err); }); return localforage.removeItem(k); };
- 實現(xiàn)邏輯:
- 同時刪除數(shù)據(jù)和對應(yīng)的過期時間。
使用示例
// 存儲數(shù)據(jù),設(shè)置過期時間為 5 分鐘 setItem('userInfo', { name: '張三', age: 28 }, 5); // 獲取數(shù)據(jù) getItem('userInfo').then((data) => { if (data) { console.log('用戶信息:', data); } else { console.log('用戶信息已過期或不存在'); } }); // 刪除數(shù)據(jù) removeItem('userInfo');
注意事項
- 異步操作:localForage 的所有方法都是異步的,返回的是 Promise,所以在獲取數(shù)據(jù)時需要使用
then
或async/await
。 - 數(shù)據(jù)類型:localForage 支持存儲多種數(shù)據(jù)類型,包括對象、數(shù)組、Blob 等。
- 錯誤處理:在實際開發(fā)中,應(yīng)對可能出現(xiàn)的錯誤進行處理,以提高代碼的健壯性。
多實例存儲
上面的代碼實例全局只使用了一個實例存儲,如果希望使用多實例存儲,可以進行簡單的修改,下面是一個使用組合函數(shù)的方式實現(xiàn)多實例存儲的代碼。
import localforage from 'localforage'; export const useLocalforage = (options: LocalForageOptions ) => { // 配置 localForage const store = localforage.createInstance({ ...options, }); // 定義一個永不過期的標志 const NEVER_EXPIRES_FLAG = -1; /** * 設(shè)置存儲項 * @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('設(shè)置過期時間失敗:', err); }); // 存儲實際數(shù)據(jù) 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) { // 未設(shè)置過期時間,視為不存在 return null; } const expiredTime = parseInt(expiredValue, 10); if (expiredTime === NEVER_EXPIRES_FLAG) { // 永不過期 return store.getItem(k) as T; } if (expiredTime > Date.now()) { // 未過期,返回數(shù)據(jù) return store.getItem(k); } else { // 已過期,刪除數(shù)據(jù) removeItem(k); removeItem(expiredKey); return null; } } catch (err) { console.error('獲取數(shù)據(jù)失敗:', 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">用戶訪問次數(shù){{ visitorCount }}次</h1> <Demo /> </template>
不同之處是使用const store = localforage.createInstance
來創(chuàng)建實例,每次使用創(chuàng)建的store 來進行操作,并且會根據(jù)命名來存放數(shù)據(jù),這對于分類管理數(shù)據(jù)非常有用。
當然如果命名相同,就會存放在一個庫中,但建議根據(jù)功能來區(qū)分數(shù)據(jù)。比如項目數(shù)據(jù)存放在一個庫中,數(shù)據(jù) mock 存放在另一個庫中。
總結(jié)
通過以上實現(xiàn),我們可以方便地使用 localForage 來存儲帶過期時間的數(shù)據(jù)。相比傳統(tǒng)的 localStorage
,localForage 提供了更強大的功能和更好的性能,適用于大多數(shù)前端存儲場景。
本案例的所有代碼托管在:multi-localforage-demo
以上就是使用localForage實現(xiàn)帶過期時間的本地存儲方案的詳細內(nèi)容,更多關(guān)于localForage本地存儲方案的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript的函數(shù)、創(chuàng)建對象、封裝、屬性和方法、繼承
從一開始接觸到j(luò)s就感覺好靈活,每個人的寫法都不一樣,比如一個function就有N種寫法2011-03-03JavaScript中具名函數(shù)的多種調(diào)用方式總結(jié)
這篇文章主要介紹了JavaScript中具名函數(shù)的多種調(diào)用方式總結(jié),本文總結(jié)了4種方法,需要的朋友可以參考下2014-11-11JS使用for循環(huán)遍歷Table的所有單元格內(nèi)容
JS遍歷Table的所有單元格內(nèi)容思路是遍歷Table的所有Row,遍歷Row中的每一列,獲取Table中單元格的內(nèi)容2014-08-08JavaScript ( (__ = !$ + $)[+$] + ({} + $)[_/_] +({} + $)[_/_
今天在網(wǎng)上看到一篇很有意思的文章(需翻墻),解釋了幾段非常有趣的 JavaScript 代碼。你猜下面這段 JavaScript 代碼是什么意思?2011-02-02