Skip to content

WXT Storage

更新日志API 参考

对扩展存储 API 的简化封装。

安装

使用 WXT

此模块已内置于 WXT 中,无需额外安装。

ts
import { storage } from '#imports';

如果你使用了自动导入功能,storage 会自动导入,甚至不需要手动导入。

不使用 WXT

安装 NPM 包:

sh
npm i @wxt-dev/storage
pnpm add @wxt-dev/storage
yarn add @wxt-dev/storage
bun add @wxt-dev/storage
ts
import { storage } from '@wxt-dev/storage';

Storage 权限

要使用 @wxt-dev/storage API,必须在 manifest 中添加 "storage" 权限:

ts
export default defineConfig({
  manifest: {
    permissions: ['storage'],
  },
});

基本用法

所有存储键名必须以其存储区域作为前缀。

ts
// ❌ 这会抛出错误
await storage.getItem('installDate');

// ✅ 这是正确的
await storage.getItem('local:installDate');

你可以使用 local:session:sync:managed:

如果你使用 TypeScript,可以在大多数方法中添加类型参数来指定键值的预期类型:

ts
await storage.getItem<number>('local:installDate');
await storage.watch<number>(
  'local:installDate',
  (newInstallDate, oldInstallDate) => {
    // ...
  },
);
await storage.getMeta<{ v: number }>('local:installDate');

对于一次性的存储字段或通用辅助函数,这种方式是可以的,但推荐使用定义存储项来添加类型安全。

监听器

要监听存储变化,请使用 storage.watch 函数。它允许你为单个键设置监听器:

ts
const unwatch = storage.watch<number>('local:counter', (newCount, oldCount) => {
  console.log('计数已变化:', { newCount, oldCount });
});

要移除监听器,请调用返回的 unwatch 函数:

ts
const unwatch = storage.watch(...);

// 稍后...
unwatch();

元数据

@wxt-dev/storage 还支持为键设置元数据,存储在 key + "$" 处。元数据是与键关联的一组属性,可以是版本号、最后修改日期等。

除了版本控制之外,你需要自行管理字段的元数据:

ts
await Promise.all([
  storage.setItem('local:preference', true),
  storage.setMeta('local:preference', { lastModified: Date.now() }),
]);

从多次调用中设置不同的元数据属性时,属性会被合并而非覆盖:

ts
await storage.setMeta('local:preference', { lastModified: Date.now() });
await storage.setMeta('local:preference', { v: 2 });

await storage.getMeta('local:preference'); // { v: 2, lastModified: 1703690746007 }

你可以移除与键关联的所有元数据,或仅移除特定属性:

ts
// 移除所有属性
await storage.removeMeta('local:preference');

// 仅移除 "lastModified" 属性
await storage.removeMeta('local:preference', 'lastModified');

// 移除多个属性
await storage.removeMeta('local:preference', ['lastModified', 'v']);

定义存储项

反复为同一个键编写键名和类型参数可能会很繁琐。作为替代方案,你可以使用 storage.defineItem 来创建一个"存储项"。

存储项包含与 storage 变量相同的 API,但你可以在一个地方配置其类型、默认值等:

ts
// utils/storage.ts
const showChangelogOnUpdate = storage.defineItem<boolean>(
  'local:showChangelogOnUpdate',
  {
    fallback: true,
  },
);

现在,你可以使用存储项来替代 storage 变量:

ts
await showChangelogOnUpdate.getValue();
await showChangelogOnUpdate.setValue(false);
await showChangelogOnUpdate.removeValue();
const unwatch = showChangelogOnUpdate.watch((newValue) => {
  // ...
});

版本控制

如果你预计存储项会随时间增长或变化,可以为其添加版本控制。定义第一个版本时,请从版本 1 开始。

例如,考虑一个存储被扩展忽略的网站列表的存储项。

ts
type IgnoredWebsiteV1 = string;

export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
  'local:ignoredWebsites',
  {
    fallback: [],
    version: 1,
  },
);
ts
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 })); 
      }, 
    }, 
  },
);
ts
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 的迁移,就可以正常工作了!

让我们看看与之前相同的忽略网站示例,但这次从一个未版本化的项开始:

ts
export const ignoredWebsites = storage.defineItem<string[]>(
  'local:ignoredWebsites',
  {
    fallback: [],
  },
);
ts
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 就会检查是否需要运行迁移,如果需要则立即执行。对存储项的值或元数据的获取或更新调用(getValuesetValueremoveValuegetMeta 等)会自动等待迁移过程完成后再实际读写值。

默认值

使用 storage.defineItem 时,有多种方式定义默认值:

  1. fallback - 当值不存在时,从 getValue 返回此值而非 null

    此选项非常适合为设置项提供默认值:

    ts
    const theme = storage.defineItem('local:theme', {
      fallback: 'dark',
    });
    const allowEditing = storage.defineItem('local:allow-editing', {
      fallback: true,
    });
  2. init - 如果存储中尚未保存值,则初始化并保存一个值。

    这非常适合需要初始化或只设置一次的值:

    ts
    const 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 都支持字符串键和已定义的存储项:

ts
const userId = storage.defineItem('local:userId');

await storage.setItems([
  { key: 'local:installDate', value: Date.now() },
  { item: userId, value: generateUserId() },
]);

请参阅 API 参考 了解所有批量 API 的类型和使用示例。