有人可能會問:既然瀏覽器里又內置得IndexedDB,而且在IndexedDB里存數據,關了瀏覽器數據也不會丟,為什么還要在瀏覽器里用SQLite?
實際上,當 IndexedDB 內的數據量增多,數據和數據之間的關系變得復雜,IndexedDB 的劣勢就凸顯出來了。我們在這方面踩過很多坑,這里就不細說了。
好了,言歸正傳:
我建議你不要使用這個工具:https://github.com/sqlite/sqlite-wasm,用起來麻煩的很,而且類型定義還有問題(看看 issue 就知道了)。
還是自己使用原始的、官方提供的 js 文件比較好。
首先到? SQLite Download Page?下載?WebAssembly & JavaScript 版本的SQLite。
下載解壓后你將得到:
我們需要的文件都在 jswasm 目錄中。
把 jswasm 目錄中的如下文件拷貝到你的根目錄下,(可以通過 https://domain.com/sqlite3.js 訪問)
接著,在你的頁面引入?sqlite3-worker1-promiser.js 腳本文件
<script src="sqlite3-worker1-promiser.js"></script>
現在我們封裝一個 TypeScript 類
class Db {dbId: string;dbFunc: (cmd: string, param: object) => Promise<{ dbId: string; messageId: string; type: string; result: any }>;constructor() {}exec(sql: string): Promise<any[]> {return new Promise((resolve, reject) => {let rows = [];this.dbFunc("exec", {dbId: this.dbId,sql,callback: (result: { columnNames: string[]; row: any[]; rowNumber: number; type: string }) => {if (result.row) {let obj = {};for (let i = 0; i < result.columnNames.length; i++) {obj[result.columnNames[i]] = result.row[i];}rows.push(obj);} else {resolve(rows);}},});});}async open() {const dbFactory = globalThis.sqlite3Worker1Promiser.v2;delete globalThis.sqlite3Worker1Promiser;const config = {debug: (...args) => console.debug("db worker debug", ...args),onunhandled: (ev) => console.error("Unhandled db worker message:", ev.data),onerror: (ev) => console.error("db worker error:", ev),};this.dbFunc = await dbFactory(config);let { dbId } = await this.dbFunc("open", {filename: "file:db.sqlite3?vfs=opfs",simulateError: 0,});this.dbId = dbId;let rows = await this.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='Job';`);if (rows.length <= 0) {await this.exec(`CREATE TABLE Job(Id VARCHAR2(36) NOT NULL PRIMARY KEY, JobInfo TEXT, RepeatType INT, StartTime BIGINT, EndTime BIGINT, ColorIndex INT);
CREATE INDEX JobInfo_Index ON Job(JobInfo);
CREATE TABLE Setting(ViewDefault INT DEFAULT 0, ViewVal INT, LangDefault INT DEFAULT 0, SkinDefault INT DEFAULT 0, AlertBefore INT);
INSERT INTO Setting (ViewDefault, ViewVal, LangDefault, SkinDefault, AlertBefore) VALUES (0, 0, 0, 0, 5);`);}let data = await this.exec(`select * from Setting;`);console.log(data);}async delDb() {const opfsRoot = await navigator.storage.getDirectory();await opfsRoot.removeEntry("db.sqlite3");}
}
export let db = new Db();
先來看打開數據庫方法:?open 。
我們使用 SQLite3 的 V2 版本的方法,其他老方法一股腦刪掉。
const dbFactory = globalThis.sqlite3Worker1Promiser.v2;
delete globalThis.sqlite3Worker1Promiser;
接著創建一個數據庫訪問方法:dbFunc,然后使用這個方法創建數據庫:db.sqlite3
this.dbFunc = await dbFactory(config);
let { dbId } = await this.dbFunc("open", {filename: "file:db.sqlite3?vfs=opfs",simulateError: 0,
});
注意,filename的路徑和參數,我們讓數據庫保存在瀏覽器的OPFS文件系統中。
OPFS 是瀏覽器提供的一種私有文件系統,屬于 File System Access API 的一部分。它的特點包括:
私有性:每個網站擁有獨立的文件系統,其他網站無法訪問。
高性能:支持原地讀寫和同步訪問,適合數據庫等對性能要求高的場景。
持久化:數據不會因刷新或關閉瀏覽器而丟失。
?數據文件創建成功后,將得到數據庫id:dbId,這是個字符串。
接下來,我們檢查數據庫中是否存在指定的表,沒有的話,就給數據庫建表。
此時就用到了SQL指令執行方法:exec
執行 SQL 指令,也是用 dbFunc 方法完成的。
如果執行的 SQL 語句本身不返回數據(例如 INSERT, UPDATE, DELETE, 或查詢結果為空的 SELECT),回調方法 callback 只會被執行一次。callback 的傳入參數 result 沒有 row 屬性。
如果執行的 SQL 語句本身返回多行數據,那么 callback 方法會被執行多次,每次都可能返回多行查詢結果,查詢結果被存儲在 result 的 row 屬性里,最后一次 callback 方法被執行時, result 沒有 row 屬性,表示查詢結束。
下面這段代碼用來根據列名構造數據對象:
if (result.row) {let obj = {};for (let i = 0; i < result.columnNames.length; i++) {obj[result.columnNames[i]] = result.row[i];}rows.push(obj);
} else {resolve(rows);
}
如果你想刪除數據庫,可以使用 delDb 方法
const opfsRoot = await navigator.storage.getDirectory() 用于獲取 OPFS 文件系統的根目錄實例
opfsRoot.removeEntry(name) 會從當前目錄中刪除名為 "db.sqlite3" 的文件或子目錄。