WXT浏览器插件开发中文教程(18)----存储详解

前言

大家好,我是倔强青铜三 。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!!!

存储

Chrome 文档Firefox 文档

您可以使用原生 API(请参阅上面的文档),使用 WXT 的内置存储 API,或从 NPM 安装一个包。

替代方案

  1. wxt/utils/storage(推荐):WXT 自带了一个围绕原生存储 API 的封装,简化了常见的使用场景。

  2. DIY:如果您正在迁移到 WXT 并且已经有了一个存储封装,可以继续使用它。将来,如果您想删除该代码,可以使用这些替代方案之一,但在迁移过程中没有理由替换正在工作的代码。

  3. 其他任何 NPM 包:有很多围绕存储 API 的封装,您可以找到喜欢的一个。这里是一些流行的:

    • webext-storage - 一个更可用的类型化存储 API,适用于 Web 扩展
    • @webext-core/storage - 一个类型安全的、类似 localStorage 的封装,围绕 Web 扩展存储 API

WXT 存储 [​](#WXT 存储 "#wxt-storage")

变更日志

扩展存储 API 的简化封装。

安装

使用 WXT [​](#使用 WXT "#with-wxt")

此模块已内置在 WXT 中,因此您不需要安装任何东西。

ts 复制代码
import { storage } from '#imports';

如果您使用自动导入,storage 会自动为您导入,因此您甚至不需要导入它!

不使用 WXT [​](#不使用 WXT "#without-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';

存储权限

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

wxt.config.ts

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');

有关可用方法的完整列表,请参见 API 参考

监听器

要监听存储更改,请使用 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) => {
  // ...
});

有关可用属性和方法的完整列表,请参见 API 参考

版本控制

如果期望存储项随着时间增长或变化,您可以为其添加版本控制。在定义项目的第一个版本时,从版本 1 开始。

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

v1

ts 复制代码
type IgnoredWebsiteV1 = string;
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
  'local:ignoredWebsites',
  {
    fallback: [],
    version: 1,
  },
);

v2

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 })); 
      }, 
    }, 
  },
);

v3

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: [],
  },
);

v2

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 参考

最后感谢阅读!欢迎关注我,微信公众号倔强青铜三。欢迎点赞收藏关注,一键三连!!!

相关推荐
货拉拉技术18 分钟前
LLM 驱动前端创新:AI 赋能营销合规实践
前端·程序员·llm
MariaH24 分钟前
深入了解vertical-align
前端
草巾冒小子38 分钟前
element-ui图片查看器
前端·vue.js·ui
光影少年1 小时前
vue3为什么不需要时间切片
前端·vue.js
Json_1 小时前
Vue 初识Hello word
前端·vue.js·深度学习
工藤孤独1 小时前
网页字体终极指南:从选择到优化加载体验
前端·css
一枚前端小姐姐1 小时前
git merge - 本地解决无权限dev分支的合并冲突
前端·git
Shi_haoliu1 小时前
各种网址整理-vue,前端,linux,ai前端开发,各种开发能用到的网址和一些有用的博客
linux·前端·javascript·vue.js·nginx·前端框架·pdf
东望1 小时前
从基础用法到源码实现:手写 Promise 的完整指南
javascript·promise