如要打造優質的離線體驗,您的 PWA 需要管理儲存空間。在快取章節中,您已瞭解快取儲存空間是保存在裝置上的一種方式。本章將說明如何管理離線資料,包括資料持續性、限制和可用的工具。
儲存空間
儲存空間不只是檔案和資產,還能包含其他類型的資料。在支援 PWA 的所有瀏覽器上,下列 API 皆可提供裝置端儲存空間:
- IndexedDB:適用於結構化資料和 blob (二進位資料) 的 NoSQL 物件儲存空間選項。
- WebStorage:使用本機儲存空間或工作階段儲存空間儲存鍵/值字串組合的方法。無法在 Service Worker 環境中使用。這個 API 為同步性質,因此不建議用於複雜的資料儲存空間。
- 快取儲存空間:如快取模組所述。
你可以在支援的平台上使用 Storage Manager API 管理所有裝置儲存空間。 Cache Storage API 和 IndexedDB 提供非同步存取 PWA 永久儲存空間的非同步存取權,而且可以從主執行緒、網路工作站和服務工作站存取。這兩個角色都扮演著重要角色,在網路不穩或不存在時,就能穩定運作 PWA。但何時該採用?
針對網路資源,以及透過網址 (例如 HTML、CSS、JavaScript、圖片、影片和音訊) 要求存取的項目,請使用 Cache Storage API。
使用 IndexedDB 儲存結構化資料。這包括需要以類似 NoSQL 方式搜尋或合併的資料,或是不需要符合網址要求的使用者專屬資料 (例如使用者專屬資料)。請注意,IndexedDB 不適用於全文搜尋。
IndexedDB
如要使用 IndexedDB,請先開啟資料庫。如果沒有資料庫,系統就會建立新的資料庫。
IndexedDB 是非同步 API,但需要進行回呼,而不是傳回 Promise。以下範例使用 Jake Archibald 的 idb 程式庫,這是適用於 IndexedDB 的小型 Promise 包裝函式。輔助程式庫不一定要使用 IndexedDB,但如果您想使用 Promise 語法,也可以選擇 idb
程式庫。
以下範例會建立資料庫來保存烹飪食譜。
建立並開啟資料庫
如何開啟資料庫:
- 使用
openDB
函式建立名為cookbook
的新索引資料庫資料庫。由於索引資料庫已版本化,因此每次變更資料庫結構時,都必須增加版本號碼。第二個參數是資料庫版本。本例設為 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 的 Web Inspector 上開啟開發人員工具後,您應會看到以下內容:
新增資料
IndexedDB 會使用交易。交易會將動作歸為一組,因此這些動作都是以單元的形式進行。這些方法有助於確保資料庫始終處於一致的狀態。如果您的應用程式正在執行多個副本,會防止同時寫入相同資料,也十分重要。 如何新增資料:
- 開始交易,並將
mode
設為readwrite
。 - 取得要加入資料的物件儲存庫。
- 使用您要儲存的資料呼叫
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 會自動設定,並由已建立索引的資料庫遞增。如果執行這段程式碼兩次,就會有兩個相同的 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 儲存空間。
儲存空間容量會由所有儲存空間選項共用,包括 Cache Storage、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 開發人員工具中,開啟「應用程式」分頁中的「儲存空間」部分,即可查看網站的配額和已使用的儲存空間詳情。
Firefox 和 Safari 未針對目前來源的所有儲存空間配額和使用情況,提供摘要畫面。
資料持續性
你可以要求瀏覽器在相容平台上使用永久儲存空間,避免在閒置後或儲存空間不足時自動刪除資料。如果授予權限,瀏覽器一律不會從儲存空間中移除資料。這項保護措施包含註冊 Service Worker 的 Service Worker 註冊資料庫、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 瀏覽器支援
網路儲存空間
檔案系統存取權
儲存空間管理工具