iT邦幫忙

2024 iThome 鐵人賽

DAY 25
1
JavaScript

可愛又迷人的 Web API系列 第 25

Day25. 另一種儲存資料的方式:IndexedDB API

  • 分享至 

  • xImage
  •  

說到儲存資料,大家第一個會想到的,應該都是 LocalStorage 或 SessionStorage 吧。

但如果今天要處理更複雜的資料查詢,或是需要管理更多的結構化數據時,LocalStorage 可能不太方便,這時候,也許可以考慮使用 IndexedDB API。我們可以在使用者的瀏覽器中,透過 IndexedDB API 建立、讀取和管理大量的結構化數據,處理類數據庫的操作,且數據量也不受限,就來看看 IndexedDB API 該怎麼使用吧!

在瀏覽器檢視 IndexedDB 資料

以 Chrome 瀏覽器為例,開啟開發者工具後,進入 Application (應用程式) 的面板,在左側找到 IndexedDB 後將他開啟,就能看到應用程式中所有的 IndexedDB 數據庫,並可以檢視各個資料表的內容。

https://mukiwu.github.io/web-api-demo/img/24-1.png

建立和管理資料庫

我們先試著建立一個資料庫吧!IndexedDB 是透過版號來管理資料庫的,每當我們需要對資料庫進行更動時(例如增加新的資料表或建立索引),就需要更新版號。

建立資料庫

使用 indexedDB.open() 建立一個 MyDatabase 的資料庫,然後再建立一張 users 表,這個表有兩個 index:nameemail。我將 email 設為 { unique: true },表示唯一值,不允許重複。

let db;
const request = indexedDB.open('MyDatabase', 1);

request.onupgradeneeded = function(event) {
  db = event.target.result;
  const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
  objectStore.createIndex('name', 'name', { unique: false });
  objectStore.createIndex('email', 'email', { unique: true });
};

request.onsuccess = function(event) {
  db = event.target.result;
  console.log('資料庫建立成功');
};

request.onerror = function(event) {
  console.error('資料庫建立失敗', event);
};

切到 IndexedDB 可以看到剛剛建立的 MyDatabase,裡面有一個 users

https://mukiwu.github.io/web-api-demo/img/24-2.png

CRUD 走一遍

建立資料庫之後,一定要來走一遍 CRUD (Create / Read / Update / Delete),範例會做四個按鈕,分別代表這四個動作。

<button onclick="createUser()">新增使用者</button>
<button onclick="readUser()">載入使用者資料</button>
<button onclick="updateUser()">更新使用者資料</button>
<button onclick="deleteUser()">刪除使用者</button>

Create: 新增使用者

function createUser() {
  const transaction = db.transaction(['users'], 'readwrite');
  const objectStore = transaction.objectStore('users');
  const user = { name: 'MUKI', email: 'muki@tw.com' };
  const request = objectStore.add(user);

  request.onsuccess = function() {
    console.log('新增成功');
  };

  request.onerror = function() {
    console.error('新增失敗');
  };
}

IndexedDB API 使用 db.transaction 來建立一個資料庫交易,你也可能聽過資料庫事務,他們都是指 database 的transaction。

什麼是資料庫交易呢?簡單來說,就是在執行資料庫指令時的一個方法,他的概念就是交易,要嘛全部交易成功、要嘛全部交易失敗。舉例來說,假設小明跟小華雙方在做買賣,小明突然反悔不賣東西給小華了,小華即使想付錢也拿不到東西,這就是交易失敗,小明跟小華不會做任何事情、也不會產生任何改變。

那麼回到 IndexedDB API,db.transaction 包含多個操作,如新增、刪除... 等,而使用 transactoin 就會保證這些操作要嘛全部成功、要嘛全部失敗,如此一來就能保證資料的完整性與一致性。

transaction 參數介紹

const transaction = db.transaction(storeNames, mode);

db.transaction 有兩個參數如下:

  • storeNames:可以接收字串或是字串陣列,如果我們只操作一個物件,你可以用字串 db.transaction('users', 'readwrite'),如果操作多個物件,可以像我一樣用字串陣列 db.transaction(['users'], 'readwrite')
  • mode:有兩個值可用
    • readonly:只能讀,不能寫
    • readwrite:可以讀也可以寫

transaction 使用流程

我們來拆解一下範例「新增使用者」的程式碼

// 建立一個新的交易
const transaction = db.transaction(['users'], 'readwrite');

// 取得要操作的儲存對象,我們在最開始建立資料庫時,已經有用 db.createObjectStore 建立 'users'
const objectStore = transaction.objectStore('users');

const user = { name: 'MUKI', email: 'muki@tw.com' };

// 增加 user 資料
const request = objectStore.add(user);

// 取得與處理對應的結果

request.onsuccess = function() {
  console.log('新增成功');
};

request.onerror = function() {
  console.error('新增失敗');
};
  

transaction 的特性

有接觸或研究過資料庫的朋友,應該對他的特性 ACID 不陌生,這邊就班門弄斧簡單分享這四個特性:

  • Atomicity (原子性):將整個交易視為一個整體,要嘛交易全部成功或全部失敗,沒有一半成功一半失敗這種東西。
  • Consistency (一致性):資料庫在交易的前後都會保持一致,可能包含數據的完整性、業務邏輯,數據表之間的關係 ... 等等。
  • Isolation (隔離性):交易執行期間,不會被其他的交易所干擾影響,他們必須要各自獨立,互不侵犯。
  • Durability (持久性):一旦交易完成,資料將永久保存在資料庫中。

Read:查詢使用者資料

接下來的查詢、更新以及刪除,也都會使用 db.transaction 做處理。

function getUserByEmail(email) {
  // 因為只要查詢使用者,所以 mode 參數可以設定為 readonly。
  const transaction = db.transaction(['users'], 'readonly);
  const objectStore = transaction.objectStore('users');
  // 設定索引的欄位
  const index = objectStore.index('email');
  const request = index.get(email);

  request.onsuccess = function() {
    if (request.result) {
      console.log('找到使用者:', request.result);
    } else {
      console.log('該使用者不存在');
    }
  };

  request.onerror = function(event) {
    console.error('搜尋失敗', event);
  };
}

getUserByEmail('muki@tw.com');

上面的例子是針對 email 欄位做搜尋,我們也可以用 getAll() 直接讀取所有使用者的資料:

const request = objectStore.getAll();

Update:更新使用者資料

使用 objectStore.get() 取得要更新的使用者資料,如果沒有像上一個範例一樣特別設定 index (const index = objectStore.index('email')),那傳入的參數預設會是使用者 ID

function updateUser() {
  const transaction = db.transaction(['users'], 'readwrite');
  const objectStore = transaction.objectStore('users');
  // 取得 ID = 1 的使用者資料
  const request = objectStore.get(1);

  request.onsuccess = function() {
    const user = request.result;
    user.name = 'MMM';
  };

  request.onerror = function() {
    console.error('讀取失敗');
  };
}

Delete:刪除使用者資料

使用 objectStore.delete() 刪除使用者資料

function deleteUser() {
  const transaction = db.transaction(['users'], 'readwrite');
  const objectStore = transaction.objectStore('users');
  // 刪除 ID = 1 的使用者資料
  const request = objectStore.delete(1);

  request.onsuccess = function() {
    console.log('刪除成功');
  };

  request.onerror = function() {
    console.error('刪除失敗');
  };
}

範例頁面

這邊提供上述的範例程式碼頁面:https://mukiwu.github.io/web-api-demo/indexeddb.html

頁面有做一些調整,我加入了簡易的表單輸入,這樣大家會看得更清楚。

小結

透過 IndexedDB API,我們可以在瀏覽器處理大量的結構化資料,可以做更多複雜的應用,下一篇會再跟大家分享幾個常見的應用,有任何問題,也歡迎留言討論。


上一篇
Day24. 深入了解 Intersection Observer API 與其應用 II
下一篇
Day26. 使用 IndexedDB API 做一個筆記儲存功能
系列文
可愛又迷人的 Web API28
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言