PWA ต้องมีการจัดการพื้นที่เก็บข้อมูลเพื่อสร้างประสบการณ์การใช้งานแบบออฟไลน์ที่ยอดเยี่ยม ในบทที่เกี่ยวกับแคช คุณได้เรียนรู้ว่าพื้นที่เก็บข้อมูลแคชเป็นตัวเลือกหนึ่งในการบันทึกข้อมูลในอุปกรณ์ ในบทนี้ เราจะแสดงวิธีจัดการข้อมูลออฟไลน์ ซึ่งรวมถึงการเก็บรักษาข้อมูล ข้อจํากัด และเครื่องมือที่ใช้ได้
พื้นที่เก็บข้อมูล
พื้นที่เก็บข้อมูลไม่ได้มีแค่ไฟล์และเนื้อหาเท่านั้น แต่อาจรวมถึงข้อมูลประเภทอื่นๆ ด้วย ในเบราว์เซอร์ทั้งหมดที่รองรับ PWA จะมี API ต่อไปนี้สําหรับพื้นที่เก็บข้อมูลในอุปกรณ์
- IndexedDB: ตัวเลือกพื้นที่เก็บข้อมูลออบเจ็กต์ NoSQL สำหรับ Structured Data และ Blob (ข้อมูลไบนารี)
- WebStorage: วิธีจัดเก็บคู่สตริงคีย์/ค่าโดยใช้พื้นที่เก็บข้อมูลในเครื่องหรือพื้นที่เก็บข้อมูลเซสชัน และไม่พร้อมใช้งานในบริบท Service Worker API นี้เป็นแบบซิงค์จึงไม่แนะนำสำหรับพื้นที่เก็บข้อมูลที่ซับซ้อน
- พื้นที่เก็บข้อมูลแคช: ตามที่อธิบายไว้ในโมดูลการแคช
คุณจัดการพื้นที่เก็บข้อมูลทั้งหมดของอุปกรณ์ได้ด้วย Storage Manager API ในแพลตฟอร์มที่รองรับ Cache Storage API และ IndexedDB ให้การเข้าถึงแบบไม่พร้อมกันสำหรับพื้นที่เก็บข้อมูลถาวรของ PWA และเข้าถึงได้จากเธรดหลัก เว็บเวิร์กเกอร์ และเวิร์กเกอร์บริการ ทั้ง 2 อย่างนี้มีบทบาทสําคัญในการทําให้ PWA ทํางานได้อย่างน่าเชื่อถือเมื่อเครือข่ายไม่เสถียรหรือไม่มีอยู่ แต่ควรใช้แต่ละรายการเมื่อใด
ใช้ Cache Storage API สำหรับทรัพยากรเครือข่าย ซึ่งเป็นสิ่งที่คุณเข้าถึงโดยการขอผ่าน URL เช่น HTML, CSS, JavaScript, รูปภาพ, วิดีโอ และเสียง
ใช้ IndexedDB เพื่อจัดเก็บ Structured Data ซึ่งรวมถึงข้อมูลที่ค้นหาได้หรือรวมกันได้ในลักษณะ NoSQL หรือข้อมูลอื่นๆ เช่น ข้อมูลที่เจาะจงผู้ใช้ซึ่งไม่จําเป็นต้องตรงกับคําขอ URL โปรดทราบว่า IndexedDB ไม่ได้ออกแบบมาเพื่อการค้นหาข้อความแบบเต็ม
IndexedDB
หากต้องการใช้ IndexedDB ให้เปิดฐานข้อมูลก่อน ซึ่งจะสร้างฐานข้อมูลใหม่หากยังไม่มี
IndexedDB เป็น API แบบแอซิงโครนัส แต่จะใช้การเรียกกลับแทนการคืนค่า Promise ตัวอย่างต่อไปนี้ใช้ idb library ของ Jake Archibald ซึ่งเป็น Wrapper ขนาดเล็กสำหรับ Promise สำหรับ IndexedDB คุณไม่จำเป็นต้องใช้ไลบรารีตัวช่วยเพื่อใช้ IndexedDB แต่หากต้องการใช้ไวยากรณ์ Promise คุณก็ใช้ไลบรารี idb
ได้
ตัวอย่างต่อไปนี้สร้างฐานข้อมูลเพื่อเก็บสูตรอาหาร
การสร้างและเปิดฐานข้อมูล
วิธีเปิดฐานข้อมูล
- ใช้ฟังก์ชัน
openDB
เพื่อสร้างฐานข้อมูล IndexedDB ใหม่ชื่อcookbook
เนื่องจากฐานข้อมูล IndexedDB มีเวอร์ชัน คุณจึงต้องเพิ่มหมายเลขเวอร์ชันทุกครั้งที่ทำการเปลี่ยนแปลงโครงสร้างฐานข้อมูล พารามิเตอร์ที่ 2 คือเวอร์ชันฐานข้อมูล ในตัวอย่างนี้มีการตั้งค่าเป็น 1 - ระบบจะส่งออบเจ็กต์การเริ่มต้นที่มี
upgrade()
Callback ไปยังopenDB()
ระบบจะเรียกใช้ฟังก์ชัน Callback เมื่อติดตั้งฐานข้อมูลเป็นครั้งแรกหรือเมื่ออัปเกรดเป็นเวอร์ชันใหม่ ฟังก์ชันนี้เป็นที่เดียวที่การดำเนินการจะเกิดขึ้นได้ การดำเนินการอาจรวมถึงการสร้างที่เก็บออบเจ็กต์ใหม่ (โครงสร้างที่ IndexedDB ใช้จัดระเบียบข้อมูล) หรือดัชนี (ที่คุณต้องการค้นหา) ซึ่งควรเป็นตำแหน่งที่ทำการย้ายข้อมูลด้วย โดยปกติแล้ว ฟังก์ชันupgrade()
จะมีคำสั่งswitch
ที่ไม่มีคำสั่งbreak
เพื่อให้แต่ละขั้นตอนทำงานตามลำดับ โดยอิงตามฐานข้อมูลเวอร์ชันเก่า
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()
พร้อมข้อมูลที่คุณบันทึกไว้ เมธอดนี้จะรับข้อมูลในรูปแบบพจนานุกรม (เป็นคู่คีย์/ค่า) และเพิ่มลงในที่เก็บออบเจ็กต์ พจนานุกรมต้องโคลนได้โดยใช้การโคลน Structured Data หากต้องการอัปเดตออบเจ็กต์ที่มีอยู่ คุณจะต้องเรียกใช้เมธอด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;
}
เมื่อเพิ่มคุกกี้แล้ว สูตรอาหารจะอยู่ในฐานข้อมูลพร้อมกับสูตรอาหารอื่นๆ IndexedDB จะตั้งค่าและเพิ่มรหัสโดยอัตโนมัติ หากคุณเรียกใช้โค้ดนี้ 2 ครั้ง คุณจะมีรายการคุกกี้ที่เหมือนกัน 2 รายการ
กำลังดึงข้อมูล
วิธีรับข้อมูลจาก 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, พื้นที่เก็บข้อมูลเว็บ และแม้แต่ไฟล์ Service Worker และทรัพยากรที่เกี่ยวข้อง
อย่างไรก็ตาม ปริมาณพื้นที่เก็บข้อมูลที่ใช้ได้จะแตกต่างกันไปตามเบราว์เซอร์ แต่คุณก็ไม่จำเป็นต้องกังวลไป เว็บไซต์อาจจัดเก็บข้อมูลได้หลายเมกะไบต์หรือแม้แต่หลายกิกะไบต์ในบางเบราว์เซอร์ ตัวอย่างเช่น 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, ฐานข้อมูล 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 ในระบบปฏิบัติการ เบราว์เซอร์อาจให้สิทธิ์พื้นที่เก็บข้อมูลถาวร
การรองรับเบราว์เซอร์ของ API
พื้นที่เก็บข้อมูลในเว็บ
การเข้าถึงระบบไฟล์
ตัวจัดการพื้นที่เก็บข้อมูล