離線資料

如要打造可靠的離線體驗,您的 PWA 需要儲存空間管理。在快取章節中,您學到了快取儲存空間是將資料儲存到裝置的其中一種選項。在本章中,我們會說明如何管理離線資料,包括資料持續性、限制和可用的工具。

儲存空間

儲存空間不僅可以存放檔案和資產,也能包含其他類型的資料。在所有支援 PWA 的瀏覽器上,下列 API 適用於裝置端儲存空間:

  • IndexedDB:適用於結構化資料和 blob (二進位資料) 的 NoSQL 物件儲存空間選項。
  • WebStorage:一種儲存鍵/值字串組合的方式,可使用本機儲存空間或工作階段儲存空間。不適用於 Service Worker。這個 API 為同步性質,因此不建議用於複雜的資料儲存空間。
  • 快取儲存空間:如快取模組所述。

在支援的平台上,您可以使用 Storage Manager API 管理所有裝置儲存空間。Cache Storage API 和索引資料庫可讓 PWA 的永久儲存空間提供非同步存取,並可從主執行緒、網路工作站和服務工作站存取。在網路不穩定或不存在時,這兩個平台都扮演重要角色,可確保 PWA 穩定運作。但何時該使用這兩種產品?

針對網路資源 (透過網址要求存取的項目,例如 HTML、CSS、JavaScript、圖片、影片和音訊),請使用 Cache Storage API

使用 IndexedDB 儲存結構化資料。這包括必須能以類似 NoSQL 的形式搜尋或合併的資料,或是不一定符合網址要求的其他使用者特定資料。請注意,IndexedDB 不是為全文搜尋而設計。

IndexedDB

如要使用 IndexedDB,請先開啟資料庫。如果沒有資料庫,則系統會建立新的資料庫。IndexedDB 是非同步 API,但會接受回呼,而非傳回 Promise。以下範例使用 Jake Archibald 的 idb 程式庫,這是 IndexedDB 的小型 Promise 包裝函式。使用 IndexedDB 時不一定要使用輔助程式庫,但如果您想使用 Promise 語法,可以選擇 idb 程式庫。

以下範例會建立資料庫來存放烹飪食譜。

建立及開啟資料庫

如要開啟資料庫,請按照下列步驟操作:

  1. 使用 openDB 函式建立新的索引資料庫資料庫,名稱為 cookbook。由於 IndexedDB 資料庫已建立版本,因此每次變更資料庫結構時,都必須增加版本號碼。第二個參數是資料庫版本。在此範例中設為 1。
  2. 系統會將包含 upgrade() 回呼的初始化物件傳遞至 openDB()。首次安裝資料庫或升級到新版本時,會呼叫回呼函式。這個函式是唯一可以執行動作的位置。動作可能包括建立新物件存放區 (IndexedDB 用來整理資料的結構),或索引 (要搜尋的索引)。這也是應能遷移資料的位置。一般來說,upgrade() 函式包含不含 break 陳述式的 switch 陳述式,可讓各個步驟根據舊版資料庫的順序執行。
import { openDB } from 'idb';

async function createDB() {
  // Using https://github.com/jakearchibald/idb
  const db = await openDB('cookbook', 1, {
    upgrade(db, oldVersion, newVersion, transaction) {
      // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
    switch(oldVersion) {
     case 0:
       // Placeholder to execute when database is created (oldVersion is 0)
     case 1:
       // Create a store of objects
       const store = db.createObjectStore('recipes', {
         // The `id` property of the object will be the key, and be incremented automatically
           autoIncrement: true,
           keyPath: 'id'
       });
       // Create an index called `name` based on the `type` property of objects in the store
       store.createIndex('type', 'type');
     }
   }
  });
}

這個範例會在 cookbook 資料庫中建立名為 recipes 的物件存放區,並將 id 屬性設為儲存庫的索引鍵,並根據 type 屬性建立名為 type 的另一個索引。

我們來看看剛建立的物件儲存庫。將食譜新增至物件存放區,並在以 Chromium 為基礎的瀏覽器或 Safari 的網頁檢查器中開啟開發人員工具後,您應該會看到:

Safari 和 Chrome 顯示 IndexedDB 內容。

新增資料

IndexedDB 使用交易。交易會將動作分組,因此可做為一個單元。這些政策有助於確保資料庫始終維持一致的狀態。此外,如果您的應用程式有多個副本正在執行,可避免同時寫入相同的資料。 如何新增資料:

  1. 啟動 mode 設為 readwrite 的交易。
  2. 取得您將在其中新增資料的物件儲存空間。
  3. 使用您要儲存的資料呼叫 add()。這個方法會以字典格式接收資料 (做為鍵/值組合),並將資料新增至物件存放區。這個字典必須透過結構化複製功能進行複製。如要更新現有物件,請改為呼叫 put() 方法。

交易具有 done 保證,可在交易成功完成時解決,或因交易錯誤而遭拒。

IDB 程式庫說明文件所述,如果您要寫入資料庫,tx.done 代表所有項目已成功提交至資料庫。然而,等待個別作業仍有助於找出導致交易失敗的錯誤。

// Using https://github.com/jakearchibald/idb
async function addData() {
  const cookies = {
      name: "Chocolate chips cookies",
      type: "dessert"
        cook_time_minutes: 25
  };
  const tx = await db.transaction('recipes', 'readwrite');
  const store = tx.objectStore('recipes');
  store.add(cookies);
  await tx.done;
}

新增 Cookie 後,食譜就會與其他方案一起儲存在資料庫中。這個 ID 會自動設定並由已建立索引的 DB 遞增。如果您執行這段程式碼兩次,就會有兩個相同的 Cookie 項目。

正在擷取資料

以下說明如何從 IndexedDB 取得資料:

  1. 啟動交易並指定物件存放區,以及選用的交易類型。
  2. 從該筆交易呼叫 objectStore()。請務必指定物件存放區名稱。
  3. 使用要取得的金鑰呼叫 get()。根據預設,儲存庫會使用其索引鍵做為索引。
// Using https://github.com/jakearchibald/idb
async function getData() {
  const tx = await db.transaction('recipes', 'readonly')
  const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
  const value = await store.get([id]);
}

儲存空間管理工具

如要正確儲存及串流網路回應,就必須瞭解如何管理 PWA 的儲存空間。

所有儲存空間選項會共用儲存空間容量,包括快取儲存空間、IndexedDB、Web Storage,甚至是 Service Worker 檔案及其依附元件。不過,可用的儲存空間容量因瀏覽器而異。您或許沒辦法用完。某些瀏覽器可在部分瀏覽器上儲存 MB 甚至是 GB 的資料。舉例來說,Chrome 可讓瀏覽器最多使用總磁碟空間的 80%,個別來源最多可使用整個磁碟空間的 60%。如果瀏覽器支援 Storage API,您可以查看應用程式仍可使用多少儲存空間、其配額,以及使用情況。下例會使用 Storage API 估算配額和用量,然後計算已使用的百分比和剩餘位元組。請注意,navigator.storage 會傳回 StorageManager 的執行個體。它有獨立的 Storage 介面,很容易造成混淆。

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

在 Chromium 開發人員工具中,如要查看網站的配額和儲存空間用量,只要開啟「應用程式」分頁的「儲存空間」部分即可。

應用程式中的 Chrome 開發人員工具、清除儲存空間部分

但是 Firefox 和 Safari 不會提供摘要畫面讓您查看目前來源的所有儲存空間配額和使用情形。

資料持續性

你可以要求瀏覽器提供相容平台的永久儲存空間,避免資料在閒置或儲存壓力後自動移除。如果授予存取權,瀏覽器絕不會從儲存空間中剔除資料。這項保護機制包括 Service Worker 註冊、IndexedDB 資料庫,以及快取儲存空間中的檔案。請注意,使用者一律須付費,且隨時能刪除儲存空間,即使瀏覽器已授予永久儲存空間亦然。

如想要求永久儲存空間,請呼叫 StorageManager.persist()。與先前一樣,StorageManager 介面是透過 navigator.storage 屬性存取。

async function persistData() {
  if (navigator.storage && navigator.storage.persist) {
    const result = await navigator.storage.persist();
    console.log(`Data persisted: ${result}`);
}

您也可以呼叫 StorageManager.persisted(),檢查目前來源是否已授予永久儲存空間。Firefox 會要求使用者授予永久儲存空間的權限。以 Chromium 為基礎的瀏覽器會根據經驗法則,判定內容對使用者的重要性,並決定持續性。Google Chrome 的其中一項條件是,例如安裝 PWA。如果使用者在作業系統中安裝了 PWA 的圖示,瀏覽器可能會授予永久儲存空間。

Mozilla Firefox 會要求使用者授予儲存空間持續性權限。

API 瀏覽器支援

網頁儲存空間

瀏覽器支援

  • 4
  • 12
  • 3.5
  • 4

資料來源

檔案系統存取權

瀏覽器支援

  • 86
  • 86
  • 111
  • 15.2

資料來源

儲存空間管理工具

瀏覽器支援

  • 55
  • 79
  • 57
  • 15.2

資料來源

資源