数据持久化
数据持久化
由于 Electron 本身体质特殊,是 Chromium 和 Node.js 的结合体,因此在实现数据持久化的时候,可以从两个方面入手:
- 基于 Node.js 的本地文件持久化
- 基于浏览器技术的持久化
设置存储目录
首先第一步我们需要设置用户数据存储目录。注意,这个目录一般不会设置在安装目录下面,因为安装目录不可靠,随时面临被清空重置的可能。
一般来讲,操作系统都会有一个默认的目录,来为应用程序存储对应的用户个性化数据。
不同的操作系统里面,对应的这个默认目录地址是不一样的。
Windows
- 用户数据目录:在 Windows 操作系统中,应用程序通常在以下路径之一存储用户数据:
C:\Users\[用户名]\AppData\Roaming\
:用于存储漫游数据,即用户在不同计算机上使用相同用户账户时可以访问的数据。C:\Users\[用户名]\AppData\Local\
:用于存储特定于单个计算机的数据。
- 程序数据目录:对于所有用户共享的应用数据,通常存储在
C:\ProgramData\
目录。
macOS
- 用户数据目录: 在macOS中,应用程序的用户数据通常存储在用户的库目录(Library)中,路径为:
/Users/[用户名]/Library/
:这个目录用于存储应用程序的配置文件、缓存和其他用户特定的数据。
- 应用支持目录:特别是,许多应用程序将数据存储在
/Users/[用户名]/Library/Application Support/
目录。
Linux
- 用户数据目录:在 Linux 系统中,用户个性化数据通常存储在用户的主目录下的隐藏文件夹中。这些目录的名称通常以点( . )开始,例如:
/home/[用户名]/.[应用程序名称]/
:用于存储该应用程序的个性化设置和数据。 - 配置文件:一些通用的配置文件可能存储在
/home/[用户名]/.config/
目录中。
在 Electron 中可以通过 app.getPath("userData") 来获取到默认的用户数据目录。
另外,在 app.getPath 方法中传入不同的参数,能够获取到不同用途的路径:
"home"
: 用户的主目录。"appData"
: 当前用户的应用程序目录。"userData"
: 对应用户个性化数据的目录。"temp"
: 临时文件目录。"exe"
: 当前的可执行文件目录。"desktop"
: 用户的桌面目录。"documents"
: 用户的文档目录。"downloads"
: 用户的下载目录。"music"
: 用户的音乐目录。"pictures"
: 用户的图片目录。"videos"
: 用户的视频目录。
例如:
console.log(app.getPath("userData")); // /Users/jie/Library/Application Support/demo
console.log(app.getPath("home")); // /Users/jie
console.log(app.getPath("desktop")); // /Users/jie/Desktop
console.log(app.getPath("documents")); // /Users/jie/Documents
console.log(app.getPath("downloads")); // /Users/jie/Downloads
console.log(app.getPath("music")); // /Users/jie/Music
有些时候,为了提升用户的体验,允许用户自己来选择将应用的用户数据存储到哪个位置,通过:
app.setPath("appData", "path");
- appData: 要重置的路径的名称
- path:具体的路径
读写本地文件
关于读写本地文件这一块儿,就是利用 Node.js 里面和文件处理相关的 fs 模块的 API。
这里再推荐一个第三方的库:fs-extra,这个库在原本的 fs 模块的基础上又做了一层封装,添加了很多更好用的 API。
原生 fs 模块,在删除一个目录的时候,如果该目录下面有子目录,子目录下面又有子目录,那么该目录是无法删除,要删除一个目录的前提就是该目录必须是空的,因此在原生 fs 模块里面,就会涉及到递归操作
const fs = require("fs");
const path = require("path");
function deleteDirectory(directoryPath) {
if (fs.existsSync(directoryPath)) {
fs.readdirSync(directoryPath).forEach((file) => {
const currentPath = path.join(directoryPath, file);
if (fs.lstatSync(currentPath).isDirectory()) {
// 递归删除子目录
deleteDirectory(currentPath);
} else {
// 删除文件
fs.unlinkSync(currentPath);
}
});
// 删除目录
fs.rmdirSync(directoryPath);
console.log(`Directory ${directoryPath} has been removed successfully.`);
} else {
console.log("Directory not found.");
}
}
// 定义要删除的目录路径
const dirPath = "./path/to/your/directory";
try {
deleteDirectory(dirPath);
} catch (err) {
console.error(`Error occurred while removing directory: ${err.message}`);
}
fs-extra
const fs = require("fs-extra");
// 定义要删除的目录路径
const directoryPath = "./path/to/your/directory";
try {
// 删除目录及其所有内容
fs.removeSync(directoryPath);
console.log(`Directory ${directoryPath} has been removed successfully.`);
} catch (err) {
console.error(`Error occurred while removing directory: ${err.message}`);
}
示例二
原生 fs 模块在创建目录之前需要先判断该目录是否存在,只有在不存在的情况下,才能够创建
原生 fs 模块相关代码:
const fs = require("fs");
const path = require("path");
const dirPath = path.join(__dirname, "exampleDir");
// 检查目录是否存在
if (!fs.existsSync(dirPath)) {
// 如果目录不存在,则创建它
fs.mkdirSync(dirPath, { recursive: true });
console.log("Directory created successfully!");
} else {
console.log("Directory already exists.");
}
fs-extra 模块提供了 ensureDir 方法:
const fs = require("fs-extra");
const path = require("path");
const dirPath = path.join(__dirname, "exampleDir");
// 如果已经存在,代码将继续执行而不会发生错误。
// 如果目录不存在,则创建它
fs.ensureDir(dirPath)
.then(() => console.log("Directory ensured successfully!"))
.catch((err) => console.error(err));
关于 fs-extra 更多内容可以参阅:https://github.com/jprichardson/node-fs-extra
第三方库
有些时候,我们要存储的数据就是一个简单 JSON 数据,那么这个时候我们可以选择 electron-store 来进行存储。
electron-store 是专为 Electron 设计,并且专门用于存储 JSON 这种轻量级数据。
我们在使用这个第三方库的时候,引入即用,都不需要指定文件的路径和文件名。
常用的方法如下:
引入和实例化
在 Electron 的主进程或渲染进程中,引入并实例化 electron-store
:
const Store = require("electron-store");
const store = new Store();
存储数据
使用 set
方法存储数据。您可以存储字符串、数字、对象等类型的数据:
// 存储一个简单的值
store.set("unicorn", "🦄");
// 存储一个对象
store.set("user", {
name: "Alice",
age: 25,
});
读取数据
使用 get
方法读取数据,如果指定的键不存在,可以返回一个默认值:
// 读取数据
console.log(store.get("unicorn")); // 输出:🦄
// 读取对象的属性
console.log(store.get("user.name")); // 输出:Alice
// 读取不存在的键,返回默认值
console.log(store.get("foo", "默认值")); // 输出:默认值
检查键是否存在
使用 has
方法检查存储中是否存在某个键:
if (store.has("unicorn")) {
console.log("Unicorn exists");
}
删除数据
使用 delete
方法删除存储中的数据:
// 删除一个键
store.delete("unicorn");
配置和选项
在实例化 electron-store
时,您可以传递一些选项来自定义其行为:
const store = new Store({
name: "my-data", // 自定义存储文件的名称,默认是 'config'
encryptionKey: "aes-256-cbc", // 加密存储的数据
cwd: "some/path", // 自定义存储文件的路径
fileExtension: "json", // 文件扩展名,默认是 'json'
});
另外,electron-store 默认是将数据存储到对应操作系统的 userData 目录下面。
- macOS:
~/Library/Application Support/YourApp
- Windows:
C:\Users\YourName\AppData\Local\YourApp
更多关于 electron-store 的使用,可以参阅:https://github.com/sindresorhus/electron-store
浏览器技术持久化
- localStorage
- IndexedDB
localStorage
在 Electron 中,如果你打开了多个 BrowserWindow 的实例,那么它们默认情况下会共享同一个 localStorage 空间。
另外,关于多个窗口是否共享 localStorage 这一点,虽然默认是多窗口共享,但是是可以进行配置的。
例如,在创建窗口二的时候,可以添加如下的配置:
const secondWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
partition: "persist:myCustomPartition",
},
});
- partition:用于定义该窗口数据存储的独立性和持久性
- persist:这是一个前缀,该前缀表明这是一个持久性的会话
- myCustomPartition:这个标识符代表该会话的唯一名称。这里就是通过不同的 partition 名称,给应用中的不同窗口创建了隔离的存储空间。
浏览器技术持久化
- localStorage
- IndexedDB
localStorage
在 Electron 中,如果你打开了多个 BrowserWindow 的实例,那么它们默认情况下会共享同一个 localStorage 空间。
另外,关于多个窗口是否共享 localStorage 这一点,虽然默认是多窗口共享,但是是可以进行配置的。
例如,在创建窗口二的时候,可以添加如下的配置:
const secondWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
partition: "persist:myCustomPartition",
},
});
- partition:用于定义该窗口数据存储的独立性和持久性
- persist:这是一个前缀,该前缀表明这是一个持久性的会话
- myCustomPartition:这个标识符代表该会话的唯一名称。这里就是通过不同的 partition 名称,给应用中的不同窗口创建了隔离的存储空间。
IndexedDB
IndexedDB 是一种低级的 API,用于在用户的浏览器中存储大量的结构化数据。这个 API 使用索引来实现对数据的高性能搜索。它允许你创建、读取、导航和写入客户端数据库中的数据。IndexedDB 对于需要在客户端存储大量数据或无需持续联网的应用程序特别有用。
基础介绍
- 数据库:IndexedDB 创建的是一个数据库,你可以在其中存储键值对。
- 对象仓库:这是数据库中的一个“表”,用于存储数据对象。
- 事务:数据的读写操作是通过事务进行的。
- 键:数据存储的标识。
- 索引:用于高效搜索数据。
打开数据库
在使用 IndexedDB 之前,需要打开一个数据库。如果指定的数据库不存在,浏览器会创建它。
let db;
const request = indexedDB.open("MyTestDatabase", 1);
request.onerror = function (event) {
// 错误处理
console.log("Database error: " + event.target.errorCode);
};
request.onsuccess = function (event) {
db = event.target.result;
};
创建对象仓库
对象仓库类似于 SQL 数据库中的表。以下是在数据库的升级过程中创建对象仓库的示例:
request.onupgradeneeded = function (event) {
const db = event.target.result;
// 创建一个对象仓库来存储我们的数据。我们将使用 "id" 作为键路径,因为我们假设它是唯一的。
const objectStore = db.createObjectStore("name", { keyPath: "id" });
// 创建一个索引来通过 name 进行搜索。
objectStore.createIndex("name", "name", { unique: false });
};
添加数据
一旦有了对象仓库,就可以往里面添加数据了。这需要在一个事务中完成。
function addData() {
const transaction = db.transaction(["name"], "readwrite");
const objectStore = transaction.objectStore("name");
const data = { id: "1", name: "Zhang San" };
const request = objectStore.add(data);
request.onsuccess = function (event) {
console.log("数据添加成功");
};
request.onerror = function (event) {
console.log("数据添加失败");
};
}
读取数据
从对象仓库中读取数据也很简单:
function readData() {
const transaction = db.transaction(["name"]);
const objectStore = transaction.objectStore("name");
const request = objectStore.get("1"); // 使用 id 读取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
if (request.result) {
console.log("Name: " + request.result.name);
} else {
console.log("未找到数据");
}
};
}
更新数据
更新数据与添加数据类似,但通常会先读取现有数据,然后进行修改。
function updateData() {
const transaction = db.transaction(["name"], "readwrite");
const objectStore = transaction.objectStore("name");
const request = objectStore.get("1");
request.onsuccess = function (event) {
const data = event.target.result;
data.name = "Li Si"; // 修改名称
const requestUpdate = objectStore.put(data);
requestUpdate.onerror = function (event) {
console.log("更新失败");
};
requestUpdate.onsuccess = function (event) {
console.log("更新成功");
};
};
}
删除数据
删除数据也很直接:
function deleteData() {
const request = db
.transaction(["name"], "readwrite")
.objectStore("name")
.delete("1");
request.onsuccess = function (event) {
console.log("数据删除成功");
};
}
以上就是 IndexedDB 的基本用法。通过这些基本操作,可以在前端应用中实现复杂的数据存储需求。不过,记得 IndexedDB 的操作都是异步的,所以你可能需要管理好回调或者使用async/await
来处理这些异步操作。
浏览器技术持久化
- localStorage
- IndexedDB
- Dexie.js
localStorage
在 Electron 中,如果你打开了多个 BrowserWindow 的实例,那么它们默认情况下会共享同一个 localStorage 空间。
另外,关于多个窗口是否共享 localStorage 这一点,虽然默认是多窗口共享,但是是可以进行配置的。
例如,在创建窗口二的时候,可以添加如下的配置:
const secondWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
partition: "persist:myCustomPartition",
},
});
- partition:用于定义该窗口数据存储的独立性和持久性
- persist:这是一个前缀,该前缀表明这是一个持久性的会话
- myCustomPartition:这个标识符代表该会话的唯一名称。这里就是通过不同的 partition 名称,给应用中的不同窗口创建了隔离的存储空间。
IndexedDB
IndexedDB 是一种低级的 API,用于在用户的浏览器中存储大量的结构化数据。这个 API 使用索引来实现对数据的高性能搜索。它允许你创建、读取、导航和写入客户端数据库中的数据。IndexedDB 对于需要在客户端存储大量数据或无需持续联网的应用程序特别有用。
基础介绍
- 数据库:IndexedDB 创建的是一个数据库,你可以在其中存储键值对。
- 对象仓库:这是数据库中的一个“表”,用于存储数据对象。
- 事务:数据的读写操作是通过事务进行的。
- 键:数据存储的标识。
- 索引:用于高效搜索数据。
打开数据库
在使用 IndexedDB 之前,需要打开一个数据库。如果指定的数据库不存在,浏览器会创建它。
let db;
const request = indexedDB.open("MyTestDatabase", 1);
request.onerror = function (event) {
// 错误处理
console.log("Database error: " + event.target.errorCode);
};
request.onsuccess = function (event) {
db = event.target.result;
};
创建对象仓库
对象仓库类似于 SQL 数据库中的表。以下是在数据库的升级过程中创建对象仓库的示例:
request.onupgradeneeded = function (event) {
const db = event.target.result;
// 创建一个对象仓库来存储我们的数据。我们将使用 "id" 作为键路径,因为我们假设它是唯一的。
const objectStore = db.createObjectStore("name", { keyPath: "id" });
// 创建一个索引来通过 name 进行搜索。
objectStore.createIndex("name", "name", { unique: false });
};
添加数据
一旦有了对象仓库,就可以往里面添加数据了。这需要在一个事务中完成。
function addData() {
const transaction = db.transaction(["name"], "readwrite");
const objectStore = transaction.objectStore("name");
const data = { id: "1", name: "Zhang San" };
const request = objectStore.add(data);
request.onsuccess = function (event) {
console.log("数据添加成功");
};
request.onerror = function (event) {
console.log("数据添加失败");
};
}
读取数据
从对象仓库中读取数据也很简单:
function readData() {
const transaction = db.transaction(["name"]);
const objectStore = transaction.objectStore("name");
const request = objectStore.get("1"); // 使用 id 读取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
if (request.result) {
console.log("Name: " + request.result.name);
} else {
console.log("未找到数据");
}
};
}
更新数据
更新数据与添加数据类似,但通常会先读取现有数据,然后进行修改。
function updateData() {
const transaction = db.transaction(["name"], "readwrite");
const objectStore = transaction.objectStore("name");
const request = objectStore.get("1");
request.onsuccess = function (event) {
const data = event.target.result;
data.name = "Li Si"; // 修改名称
const requestUpdate = objectStore.put(data);
requestUpdate.onerror = function (event) {
console.log("更新失败");
};
requestUpdate.onsuccess = function (event) {
console.log("更新成功");
};
};
}
删除数据
删除数据也很直接:
function deleteData() {
const request = db
.transaction(["name"], "readwrite")
.objectStore("name")
.delete("1");
request.onsuccess = function (event) {
console.log("数据删除成功");
};
}
以上就是 IndexedDB 的基本用法。通过这些基本操作,可以在前端应用中实现复杂的数据存储需求。不过,记得 IndexedDB 的操作都是异步的,所以你可能需要管理好回调或者使用async/await
来处理这些异步操作。
Dexie.js
除了使用浏览器原生的 IndexedDB 以外,我们还可以使用 Dexie.js,该第三方库提供了更简洁、更易用的 API。
IndexedDB
- 原生 Web API:IndexedDB 是一个低级的 API,直接内置于现代浏览器中,用于在客户端存储大量结构化数据。
- 复杂性:直接使用 IndexedDB 可能相当复杂,主要是因为它的异步性质和繁琐的错误处理。它的 API 设计更偏向于底层,提供了大量的灵活性,但也使得简单操作变得复杂。
- 事务管理:IndexedDB 需要显式地处理事务。事务、对象存储、索引等需要仔细管理和协调。
- 无包装器:直接使用 IndexedDB 意味着编写更多的引导和设置代码,例如处理数据库的版本升级逻辑。
Dexie.js
- 封装库:Dexie.js 是一个对 IndexedDB 进行封装的库,提供了一个简单、更易于理解和使用的 API。
- 简化的操作:通过 Dexie.js,复杂的 IndexedDB 操作变得更简单。例如,它简化了异步操作的处理,使得使用 promises 和 async/await 变得直观。
- 错误处理:Dexie.js 提供了更加友好和简洁的错误处理方式。
- 强化的功能:Dexie.js 增加了一些额外的功能,如简化的索引查询和批量操作。
- 事务管理:Dexie.js 简化了事务管理。你仍然需要理解 IndexedDB 的事务概念,但 Dexie.js 提供了更简单的方法来处理它们。
- 易于升级:在 Dexie.js 中,处理数据库的版本升级更加简单和直观。
Dexie.js 的优势
- 更简洁的代码:使用 Dexie.js 可以写出更清晰、更简洁的代码,尤其是在处理复杂查询和大量的异步操作时。
- 易于维护:由于 API 更加简单,维护和更新使用 Dexie.js 编写的代码通常比直接使用 IndexedDB 更容易。
- 更好的错误处理:Dexie.js 提供了更友好的错误处理机制,有助于更容易地诊断问题。
- 社区支持:Dexie.js 拥有一个活跃的社区,提供了丰富的文档和社区支持。
下面简单介绍一下它的基本语法:
let db = new Dexie("testDb");
db.version(1).stores({ articles: "id", settings: "id" });
第一行创建一个名为 testDb 的 IndexedDB 数据库。第二行中的 db.version(1) 表示数据库的版本。
在 IndexedDB 中,有版本的概念,例如假设现在的应用的数据库版本号为 1(默认值也是 1),新版本应用希望更新数据结构,可以把数据库版本号设置为 2。当用户打开应用访问数据时,会触发 IndexedDB 的 upgradeneeded 事件,我们可以在此事件中完成数据的迁移工作。
在 Dexie.js 中,对 IndexedDB 的版本 API 进行了封装,所以在上面的代码中,我们使用 db.version 方法获得当前版本的实例,然后调用实例方法 stores,并传入数据结构对象。
数据结构对象相当于传统数据库的表,与传统数据库不同的是,我们不必为数据结构对象指定每一个字段的字段名,此处我们为 IndexedDB 添加了两个表 articles 和 settings,它们都有一个必备字段 id,其他字段可以在写入数据时临时决定。
将来如果版本更新,数据库版本号变为 2 时,数据库增加了一张表 users,代码如下:
db.version(2).stores({ articles: "id", settings: "id", users: "id" });
此时 Dexie.js 会为我们进行相应的处理,在增加新的表的同时,原有表以及表里面的数据不变。这为我们从容地控制客户端数据库版本提供了强有力的支撑。
下面来看一下使用 Dexie.js 进行常用数据操作的代码:
// 增加数据
await db.articles.add({ id: 0, title: "test" });
// 查询数据
await db.articles.filter((article) => article.title === "test");
// 修改数据
await db.articles.put({ id: 0, title: "testtest" });
// 删除数据
await db.articles.delete(id);
// 排序数据
await db.articles.orderBy("title");
注意,上面的代码中使用到了 await 关键字,所以使用的时候,应该放在 async 标记的函数里面才能正常执行。
更多有关 Dexie.js 的使用,可以参阅官网:https://dexie.org/
浏览器技术持久化
- localStorage
- IndexedDB
- Dexie.js
localStorage
在 Electron 中,如果你打开了多个 BrowserWindow 的实例,那么它们默认情况下会共享同一个 localStorage 空间。
另外,关于多个窗口是否共享 localStorage 这一点,虽然默认是多窗口共享,但是是可以进行配置的。
例如,在创建窗口二的时候,可以添加如下的配置:
const secondWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
partition: "persist:myCustomPartition",
},
});
- partition:用于定义该窗口数据存储的独立性和持久性
- persist:这是一个前缀,该前缀表明这是一个持久性的会话
- myCustomPartition:这个标识符代表该会话的唯一名称。这里就是通过不同的 partition 名称,给应用中的不同窗口创建了隔离的存储空间。
IndexedDB
IndexedDB 是一种低级的 API,用于在用户的浏览器中存储大量的结构化数据。这个 API 使用索引来实现对数据的高性能搜索。它允许你创建、读取、导航和写入客户端数据库中的数据。IndexedDB 对于需要在客户端存储大量数据或无需持续联网的应用程序特别有用。
基础介绍
- 数据库:IndexedDB 创建的是一个数据库,你可以在其中存储键值对。
- 对象仓库:这是数据库中的一个“表”,用于存储数据对象。
- 事务:数据的读写操作是通过事务进行的。
- 键:数据存储的标识。
- 索引:用于高效搜索数据。
打开数据库
在使用 IndexedDB 之前,需要打开一个数据库。如果指定的数据库不存在,浏览器会创建它。
let db;
const request = indexedDB.open("MyTestDatabase", 1);
request.onerror = function (event) {
// 错误处理
console.log("Database error: " + event.target.errorCode);
};
request.onsuccess = function (event) {
db = event.target.result;
};
创建对象仓库
对象仓库类似于 SQL 数据库中的表。以下是在数据库的升级过程中创建对象仓库的示例:
request.onupgradeneeded = function (event) {
const db = event.target.result;
// 创建一个对象仓库来存储我们的数据。我们将使用 "id" 作为键路径,因为我们假设它是唯一的。
const objectStore = db.createObjectStore("name", { keyPath: "id" });
// 创建一个索引来通过 name 进行搜索。
objectStore.createIndex("name", "name", { unique: false });
};
添加数据
一旦有了对象仓库,就可以往里面添加数据了。这需要在一个事务中完成。
function addData() {
const transaction = db.transaction(["name"], "readwrite");
const objectStore = transaction.objectStore("name");
const data = { id: "1", name: "Zhang San" };
const request = objectStore.add(data);
request.onsuccess = function (event) {
console.log("数据添加成功");
};
request.onerror = function (event) {
console.log("数据添加失败");
};
}
读取数据
从对象仓库中读取数据也很简单:
function readData() {
const transaction = db.transaction(["name"]);
const objectStore = transaction.objectStore("name");
const request = objectStore.get("1"); // 使用 id 读取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
if (request.result) {
console.log("Name: " + request.result.name);
} else {
console.log("未找到数据");
}
};
}
更新数据
更新数据与添加数据类似,但通常会先读取现有数据,然后进行修改。
function updateData() {
const transaction = db.transaction(["name"], "readwrite");
const objectStore = transaction.objectStore("name");
const request = objectStore.get("1");
request.onsuccess = function (event) {
const data = event.target.result;
data.name = "Li Si"; // 修改名称
const requestUpdate = objectStore.put(data);
requestUpdate.onerror = function (event) {
console.log("更新失败");
};
requestUpdate.onsuccess = function (event) {
console.log("更新成功");
};
};
}
删除数据
删除数据也很直接:
function deleteData() {
const request = db
.transaction(["name"], "readwrite")
.objectStore("name")
.delete("1");
request.onsuccess = function (event) {
console.log("数据删除成功");
};
}
以上就是 IndexedDB 的基本用法。通过这些基本操作,可以在前端应用中实现复杂的数据存储需求。不过,记得 IndexedDB 的操作都是异步的,所以你可能需要管理好回调或者使用async/await
来处理这些异步操作。
Dexie.js
除了使用浏览器原生的 IndexedDB 以外,我们还可以使用 Dexie.js,该第三方库提供了更简洁、更易用的 API。
IndexedDB
- 原生 Web API:IndexedDB 是一个低级的 API,直接内置于现代浏览器中,用于在客户端存储大量结构化数据。
- 复杂性:直接使用 IndexedDB 可能相当复杂,主要是因为它的异步性质和繁琐的错误处理。它的 API 设计更偏向于底层,提供了大量的灵活性,但也使得简单操作变得复杂。
- 事务管理:IndexedDB 需要显式地处理事务。事务、对象存储、索引等需要仔细管理和协调。
- 无包装器:直接使用 IndexedDB 意味着编写更多的引导和设置代码,例如处理数据库的版本升级逻辑。
Dexie.js
- 封装库:Dexie.js 是一个对 IndexedDB 进行封装的库,提供了一个简单、更易于理解和使用的 API。
- 简化的操作:通过 Dexie.js,复杂的 IndexedDB 操作变得更简单。例如,它简化了异步操作的处理,使得使用 promises 和 async/await 变得直观。
- 错误处理:Dexie.js 提供了更加友好和简洁的错误处理方式。
- 强化的功能:Dexie.js 增加了一些额外的功能,如简化的索引查询和批量操作。
- 事务管理:Dexie.js 简化了事务管理。你仍然需要理解 IndexedDB 的事务概念,但 Dexie.js 提供了更简单的方法来处理它们。
- 易于升级:在 Dexie.js 中,处理数据库的版本升级更加简单和直观。
Dexie.js 的优势
- 更简洁的代码:使用 Dexie.js 可以写出更清晰、更简洁的代码,尤其是在处理复杂查询和大量的异步操作时。
- 易于维护:由于 API 更加简单,维护和更新使用 Dexie.js 编写的代码通常比直接使用 IndexedDB 更容易。
- 更好的错误处理:Dexie.js 提供了更友好的错误处理机制,有助于更容易地诊断问题。
- 社区支持:Dexie.js 拥有一个活跃的社区,提供了丰富的文档和社区支持。
下面简单介绍一下它的基本语法:
let db = new Dexie("testDb");
db.version(1).stores({ articles: "id", settings: "id" });
第一行创建一个名为 testDb 的 IndexedDB 数据库。第二行中的 db.version(1) 表示数据库的版本。
在 IndexedDB 中,有版本的概念,例如假设现在的应用的数据库版本号为 1(默认值也是 1),新版本应用希望更新数据结构,可以把数据库版本号设置为 2。当用户打开应用访问数据时,会触发 IndexedDB 的 upgradeneeded 事件,我们可以在此事件中完成数据的迁移工作。
在 Dexie.js 中,对 IndexedDB 的版本 API 进行了封装,所以在上面的代码中,我们使用 db.version 方法获得当前版本的实例,然后调用实例方法 stores,并传入数据结构对象。
数据结构对象相当于传统数据库的表,与传统数据库不同的是,我们不必为数据结构对象指定每一个字段的字段名,此处我们为 IndexedDB 添加了两个表 articles 和 settings,它们都有一个必备字段 id,其他字段可以在写入数据时临时决定。
将来如果版本更新,数据库版本号变为 2 时,数据库增加了一张表 users,代码如下:
db.version(2).stores({ articles: "id", settings: "id", users: "id" });
此时 Dexie.js 会为我们进行相应的处理,在增加新的表的同时,原有表以及表里面的数据不变。这为我们从容地控制客户端数据库版本提供了强有力的支撑。
下面来看一下使用 Dexie.js 进行常用数据操作的代码:
// 增加数据
await db.articles.add({ id: 0, title: "test" });
// 查询数据
await db.articles.filter((article) => article.title === "test");
// 修改数据
await db.articles.put({ id: 0, title: "testtest" });
// 删除数据
await db.articles.delete(id);
// 排序数据
await db.articles.orderBy("title");
注意,上面的代码中使用到了 await 关键字,所以使用的时候,应该放在 async 标记的函数里面才能正常执行。
更多有关 Dexie.js 的使用,可以参阅官网:https://dexie.org/