Skip to content

IndexedDB第三方库 localforage

· 7 min

背景:为啥会有它?#

localStorage 用起来顺手,但就像“只有锤子,看啥都像钉子”:

IndexedDB 硬实力很强,但门槛也不低:事务、游标、版本升级……要是你只想“快点把本地缓存这摊活干完”,学习成本就有点“压榨脑力”。

localForage 的目标很朴素:用 localStorage 一样的脑回路,享受 IndexedDB 的性能与容量。它会优先用 IndexedDB,兜不住再退回 WebSQL(历史原因,现在基本用不上)/localStorage。你用的始终是一套 Promise 风格的 API,存啥都行(对象、数组、Blob、ArrayBuffer 都 OK)。


API 概览:就这几个,够吃一年#

Tips:


最小示例:两分钟就能跑#

安装

Terminal window
npm i localforage
# 或 pnpm add localforage / yarn add localforage

初始化 + 基本读写

import localforage from 'localforage'
// 建议:自己定义一个实例,别用默认的,方便隔离
const db = localforage.createInstance({
name: 'my-app',
storeName: 'kv', // 只能小写字母、数字、下划线
})
async function demo() {
await db.setItem('user', { id: 1, name: 'Ava' })
const u = await db.getItem<{ id: number; name: string }>('user')
console.log('user:', u)
await db.setItem('token', 'abc123')
console.log('keys:', await db.keys())
console.log('length:', await db.length())
await db.removeItem('token')
// await db.clear() // 清空当前实例
}
demo()

逐步扩展:把常见需求一锅端#

1) 明确优先级:优先 IndexedDB,不行就回退#

await db.setDriver([
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
])
console.log('driver:', db.driver()) // 实际使用的驱动

大多数现代浏览器都会用上 IndexedDB;遇到“无痕模式/受限环境”,会自动降级到 localStorage。不折腾,省心。

2) 多实例隔离:一人一锅,互不串味#

export const cacheAuth = localforage.createInstance({
name: 'my-app',
storeName: 'auth', // 登录态
})
export const cacheBiz = localforage.createInstance({
name: 'my-app',
storeName: 'biz', // 业务缓存
})

好处是清理和迁移都更有“颗粒度”,不会一刀切全删。

3) 存二进制:图片、文件说来就来#

async function saveAvatar(blob: Blob) {
await cacheBiz.setItem('avatar', blob) // 直接丢 Blob
}
async function loadAvatar(): Promise<Blob | null> {
return cacheBiz.getItem<Blob>('avatar')
}

localForage 内部处理好了序列化,你不必自己转 Base64 折腾。

4) 做个 TTL:到点就失效(它没内置,手搓一个)#

type Box<T> = { value: T; expiresAt?: number }
async function setWithTTL<T>(key: string, value: T, ms?: number) {
const box: Box<T> = { value }
if (ms) box.expiresAt = Date.now() + ms
await cacheBiz.setItem(key, box)
}
async function getWithTTL<T>(key: string): Promise<T | null> {
const box = await cacheBiz.getItem<Box<T>>(key)
if (!box) return null
if (box.expiresAt && Date.now() > box.expiresAt) {
await cacheBiz.removeItem(key)
return null
}
return box.value
}

简单粗暴但好用,常见的“列表缓存 10 分钟”就靠它。

5) 批量与遍历:该快的时候别磨叽#

// 批量
await Promise.all([
db.setItem('a', 1),
db.setItem('b', 2),
db.setItem('c', 3),
])
// 遍历
await db.iterate((value, key, i) => {
console.log(i, key, value)
})

6) React/Vue 里用:状态同步一把梭#

React 例子(首屏从本地读,网络回来再刷新):

import { useEffect, useState } from 'react'
import localforage from 'localforage'
const postsDB = localforage.createInstance({ name: 'my-app', storeName: 'posts' })
export function usePosts() {
const [posts, setPosts] = useState<any[] | null>(null)
useEffect(() => {
let cancelled = false
;(async () => {
const cached = await postsDB.getItem<any[]>('list')
if (!cancelled && cached) setPosts(cached)
const fresh = await fetch('/api/posts').then(r => r.json())
if (!cancelled) {
setPosts(fresh)
postsDB.setItem('list', fresh)
}
})()
return () => { cancelled = true }
}, [])
return posts
}

体验就是“先有,再新”,丝滑。

7) 版本迁移:轻量做法就够了#

场景:你之前用 kv 存了用户数据,现在想分成 authprofile

async function migrate() {
const old = localforage.createInstance({ name: 'my-app', storeName: 'kv' })
const auth = localforage.createInstance({ name: 'my-app', storeName: 'auth' })
const profile = localforage.createInstance({ name: 'my-app', storeName: 'profile' })
await old.iterate(async (value, key) => {
if (key.startsWith('token')) await auth.setItem(key, value)
if (key.startsWith('user:')) await profile.setItem(key, value)
})
// 观察一段时间后再 clear,别立刻“忍痛割爱”
}

复杂到“表级事务”的诉求,建议直接上 Dexie 之类的 ORM 风格库。


小结与常见坑#

常见坑清单

一句话:localForage 就是一把顺手的小扳手,日常修修补补够用又省心;真要大修大改,别犹豫,换上专业工具。