如要打造穩定的離線體驗,PWA 需要儲存空間管理功能。在快取章節中,您瞭解到快取儲存空間是將資料儲存在裝置上的其中一個選項。本章將說明如何管理離線資料,包括資料持續性、限制和可用工具。
儲存空間
儲存空間不僅用於檔案和資產,也可用於其他類型的資料。在所有支援 PWA 的瀏覽器中,裝置端儲存空間都提供下列 API:
- IndexedDB:NoSQL 物件儲存空間選項,適用於結構化資料和 Blob (二進位資料)。
- WebStorage:使用本機儲存空間或工作階段儲存空間儲存鍵/值字串組合。這項功能不適用於服務工作人員環境。這個 API 是同步的,因此不建議用於複雜的資料儲存作業。
- 快取儲存空間:如快取單元所述。
在支援的平台上,您可以使用 Storage Manager API 管理所有裝置儲存空間。 Cache Storage API 和 IndexedDB 可為 PWA 提供非同步的永久儲存空間存取權,並可從主執行緒、網頁工作站和服務工作站存取。兩者都扮演重要角色,可確保 PWA 在網路不穩定或沒有網路時穩定運作。但何時該使用哪一種?
使用 Cache Storage API 處理網路資源,也就是透過網址要求存取的項目,例如 HTML、CSS、JavaScript、圖片、影片和音訊。
使用 IndexedDB 儲存結構化資料。包括需要以類似 NoSQL 的方式搜尋或合併的資料,或是其他資料 (例如不一定符合網址要求的使用者專屬資料)。請注意,IndexedDB 並非專為全文搜尋設計。
IndexedDB
如要使用 IndexedDB,請先開啟資料庫。如果沒有資料庫,這項作業會建立一個。
IndexedDB 是非同步 API,但會採用回呼,而不是傳回 Promise。以下範例使用 Jake Archibald 的 idb 程式庫,這是 IndexedDB 的小型 Promise 包裝函式。使用 IndexedDB 時不一定要有輔助程式庫,但如果您想使用 Promise 語法,可以選擇 idb
程式庫。
以下範例會建立資料庫來儲存食譜。
建立及開啟資料庫
如要開啟資料庫,請按照下列步驟操作:
- 使用
openDB
函式建立名為cookbook
的新 IndexedDB 資料庫。由於 IndexedDB 資料庫有版本,因此每當變更資料庫結構時,您都需要增加版本號碼。第二個參數是資料庫版本。在本範例中,此值設為 1。 - 含有
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 上開啟網頁檢查器後,您應該會看到以下內容:
新增資料
IndexedDB 會使用交易。交易會將動作分組,因此會以單元的形式發生。確保資料庫一律處於一致狀態。如果您執行多個應用程式副本,這些鎖定也十分重要,可防止同時寫入相同資料。如要新增資料,請按照下列步驟操作:
- 啟動交易,並將
mode
設為readwrite
。 - 取得物件存放區,您將在其中新增資料。
- 使用要儲存的資料呼叫
add()
。這個方法會以字典形式 (鍵/值組合) 接收資料,並將資料新增至物件存放區。字典必須可使用結構化複製功能複製。如要更新現有物件,請改為呼叫put()
方法。
交易會提供 done
promise,在交易成功完成時解析,或在發生交易錯誤時拒絕。
如 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 後,方案就會與其他方案一起儲存在資料庫中。indexedDB 會自動設定並遞增 ID。如果您執行這段程式碼兩次,就會有兩個相同的 Cookie 項目。
正在擷取資料
以下說明如何從 IndexedDB 取得資料:
- 開始交易並指定物件商店,以及交易類型 (選用)。
- 從該交易呼叫
objectStore()
。請務必指定物件商店名稱。 - 使用要取得的鍵呼叫
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、網路儲存空間,甚至是服務工作人員檔案及其依附元件) 都會共用儲存空間容量。
不過,可用的儲存空間大小會因瀏覽器而異。您不太可能用完儲存空間,因為在某些瀏覽器上,網站可以儲存 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 開發人員工具中,您可以開啟「應用程式」分頁中的「儲存空間」部分,查看網站的配額,以及儲存空間用量 (依使用項目細分)。
Firefox 和 Safari 不提供摘要畫面,無法查看目前來源的所有儲存空間配額和用量。
資料持續性
在相容的平台上,您可以要求瀏覽器提供永久儲存空間,避免系統在閒置或儲存空間不足時自動清除資料。如果獲得授權,瀏覽器就不會從儲存空間逐出資料。這項保護機制包括服務工作人員註冊、IndexedDB 資料庫,以及快取儲存空間中的檔案。請注意,使用者一律擁有控制權,隨時可以刪除儲存空間,即使瀏覽器已授予永久儲存空間亦然。
如要要求永久儲存空間,請呼叫 StorageManager.persist()
。與先前一樣,您可透過 navigator.storage
屬性存取 StorageManager
介面。
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 的圖示,瀏覽器可能會授予永久儲存空間。
API 瀏覽器支援
Web Storage
檔案系統存取權
儲存空間管理工具