WXT Storage
对扩展存储 API 的简化封装。
安装
使用 WXT
此模块已内置于 WXT 中,无需额外安装。
import { storage } from '#imports';如果你使用了自动导入功能,storage 会自动导入,甚至不需要手动导入。
不使用 WXT
安装 NPM 包:
npm i @wxt-dev/storage
pnpm add @wxt-dev/storage
yarn add @wxt-dev/storage
bun add @wxt-dev/storageimport { storage } from '@wxt-dev/storage';Storage 权限
要使用 @wxt-dev/storage API,必须在 manifest 中添加 "storage" 权限:
export default defineConfig({
manifest: {
permissions: ['storage'],
},
});基本用法
所有存储键名必须以其存储区域作为前缀。
// ❌ 这会抛出错误
await storage.getItem('installDate');
// ✅ 这是正确的
await storage.getItem('local:installDate');你可以使用 local:、session:、sync: 或 managed:。
如果你使用 TypeScript,可以在大多数方法中添加类型参数来指定键值的预期类型:
await storage.getItem<number>('local:installDate');
await storage.watch<number>(
'local:installDate',
(newInstallDate, oldInstallDate) => {
// ...
},
);
await storage.getMeta<{ v: number }>('local:installDate');对于一次性的存储字段或通用辅助函数,这种方式是可以的,但推荐使用定义存储项来添加类型安全。
监听器
要监听存储变化,请使用 storage.watch 函数。它允许你为单个键设置监听器:
const unwatch = storage.watch<number>('local:counter', (newCount, oldCount) => {
console.log('计数已变化:', { newCount, oldCount });
});要移除监听器,请调用返回的 unwatch 函数:
const unwatch = storage.watch(...);
// 稍后...
unwatch();元数据
@wxt-dev/storage 还支持为键设置元数据,存储在 key + "$" 处。元数据是与键关联的一组属性,可以是版本号、最后修改日期等。
除了版本控制之外,你需要自行管理字段的元数据:
await Promise.all([
storage.setItem('local:preference', true),
storage.setMeta('local:preference', { lastModified: Date.now() }),
]);从多次调用中设置不同的元数据属性时,属性会被合并而非覆盖:
await storage.setMeta('local:preference', { lastModified: Date.now() });
await storage.setMeta('local:preference', { v: 2 });
await storage.getMeta('local:preference'); // { v: 2, lastModified: 1703690746007 }你可以移除与键关联的所有元数据,或仅移除特定属性:
// 移除所有属性
await storage.removeMeta('local:preference');
// 仅移除 "lastModified" 属性
await storage.removeMeta('local:preference', 'lastModified');
// 移除多个属性
await storage.removeMeta('local:preference', ['lastModified', 'v']);定义存储项
反复为同一个键编写键名和类型参数可能会很繁琐。作为替代方案,你可以使用 storage.defineItem 来创建一个"存储项"。
存储项包含与 storage 变量相同的 API,但你可以在一个地方配置其类型、默认值等:
// utils/storage.ts
const showChangelogOnUpdate = storage.defineItem<boolean>(
'local:showChangelogOnUpdate',
{
fallback: true,
},
);现在,你可以使用存储项来替代 storage 变量:
await showChangelogOnUpdate.getValue();
await showChangelogOnUpdate.setValue(false);
await showChangelogOnUpdate.removeValue();
const unwatch = showChangelogOnUpdate.watch((newValue) => {
// ...
});版本控制
如果你预计存储项会随时间增长或变化,可以为其添加版本控制。定义第一个版本时,请从版本 1 开始。
例如,考虑一个存储被扩展忽略的网站列表的存储项。
type IgnoredWebsiteV1 = string;
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
'local:ignoredWebsites',
{
fallback: [],
version: 1,
},
);import { nanoid } from 'nanoid';
type IgnoredWebsiteV1 = string;
interface IgnoredWebsiteV2 {
id: string;
website: string;
}
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>(
'local:ignoredWebsites',
{
fallback: [],
version: 1,
version: 2,
migrations: {
// 从 v1 迁移到 v2 时运行
2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => {
return websites.map((website) => ({ id: nanoid(), website }));
},
},
},
);import { nanoid } from 'nanoid';
type IgnoredWebsiteV1 = string;
interface IgnoredWebsiteV2 {
id: string;
website: string;
}
interface IgnoredWebsiteV3 {
id: string;
website: string;
enabled: boolean;
}
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>(
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV3[]>(
'local:ignoredWebsites',
{
fallback: [],
version: 2,
version: 3,
migrations: {
// 从 v1 迁移到 v2 时运行
2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => {
return websites.map((website) => ({ id: nanoid(), website }));
},
// 从 v2 迁移到 v3 时运行
3: (websites: IgnoredWebsiteV2[]): IgnoredWebsiteV3[] => {
return websites.map((website) => ({ ...website, enabled: true }));
},
},
},
);INFO
在内部,使用名为 v 的元数据属性来跟踪值的当前版本。
在这个例子中,我们预料到忽略的网站列表将来可能会发生变化,因此从一开始就设置了带版本控制的存储项。
实际上,你通常不会在需要更改存储项的结构之前意识到它需要版本控制。好在为未版本化的存储项添加版本控制非常简单。
当找不到先前版本时,WXT 假定版本为 1。这意味着你只需设置 version: 2 并添加 2 的迁移,就可以正常工作了!
让我们看看与之前相同的忽略网站示例,但这次从一个未版本化的项开始:
export const ignoredWebsites = storage.defineItem<string[]>(
'local:ignoredWebsites',
{
fallback: [],
},
);import { nanoid } from 'nanoid';
// 为第一个版本追溯添加类型
type IgnoredWebsiteV1 = string;
interface IgnoredWebsiteV2 {
id: string;
website: string;
}
export const ignoredWebsites = storage.defineItem<string[]>(
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>(
'local:ignoredWebsites',
{
fallback: [],
version: 2,
migrations: {
// 从 v1 迁移到 v2 时运行
2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => {
return websites.map((website) => ({ id: nanoid(), website }));
},
},
},
);运行迁移
一旦调用 storage.defineItem,WXT 就会检查是否需要运行迁移,如果需要则立即执行。对存储项的值或元数据的获取或更新调用(getValue、setValue、removeValue、getMeta 等)会自动等待迁移过程完成后再实际读写值。
默认值
使用 storage.defineItem 时,有多种方式定义默认值:
fallback- 当值不存在时,从getValue返回此值而非null。此选项非常适合为设置项提供默认值:
tsconst theme = storage.defineItem('local:theme', { fallback: 'dark', }); const allowEditing = storage.defineItem('local:allow-editing', { fallback: true, });init- 如果存储中尚未保存值,则初始化并保存一个值。这非常适合需要初始化或只设置一次的值:
tsconst userId = storage.defineItem('local:user-id', { init: () => globalThis.crypto.randomUUID(), }); const installDate = storage.defineItem('local:install-date', { init: () => new Date().getTime(), });值会立即被初始化到存储中。
批量操作
当需要获取或设置多个存储值时,可以执行批量操作以减少单独的存储调用次数来提升性能。storage API 提供了几种批量操作方法:
getItems- 一次获取多个值。getMetas- 一次获取多个项的元数据。setItems- 一次设置多个值。setMetas- 一次设置多个项的元数据。removeItems- 一次移除多个值(以及可选的元数据)。
所有这些 API 都支持字符串键和已定义的存储项:
const userId = storage.defineItem('local:userId');
await storage.setItems([
{ key: 'local:installDate', value: Date.now() },
{ item: userId, value: generateUserId() },
]);请参阅 API 参考 了解所有批量 API 的类型和使用示例。