ข้อมูลออฟไลน์

PWA ของคุณต้องมีการจัดการพื้นที่เก็บข้อมูลเพื่อมอบประสบการณ์การใช้งานแบบออฟไลน์ที่ยอดเยี่ยม ในบทการแคช คุณได้ทราบว่าพื้นที่เก็บข้อมูลแคชเป็นตัวเลือกหนึ่งในการบันทึกข้อมูลลงในอุปกรณ์ ในบทนี้ เราจะแสดงวิธีจัดการข้อมูลออฟไลน์ รวมถึงความต่อเนื่องของข้อมูล ขีดจำกัด และเครื่องมือที่ใช้ได้

พื้นที่เก็บข้อมูล

พื้นที่เก็บข้อมูลไม่ได้มีเพียงแค่ไฟล์และเนื้อหาเท่านั้น แต่ยังรวมถึงข้อมูลประเภทอื่นๆ ด้วย ในเบราว์เซอร์ทั้งหมดที่รองรับ PWA จะมี API ต่อไปนี้ให้บริการสําหรับพื้นที่เก็บข้อมูลในอุปกรณ์

  • IndexedDB: ตัวเลือกพื้นที่เก็บข้อมูลออบเจ็กต์ NoSQL สำหรับข้อมูลที่มีโครงสร้างและ BLOB (ข้อมูลไบนารี)
  • WebStorage: วิธีจัดเก็บคู่คีย์/ค่าโดยใช้พื้นที่เก็บข้อมูลในเครื่องหรือพื้นที่เก็บข้อมูลเซสชัน ไม่พร้อมใช้งานในบริบทของ Service Worker API นี้เป็นแบบซิงโครนัส เราจึงไม่แนะนำสำหรับการจัดเก็บข้อมูลที่ซับซ้อน
  • พื้นที่เก็บข้อมูลแคช: ตามที่ระบุไว้ในโมดูลการแคช

คุณสามารถจัดการพื้นที่เก็บข้อมูลของอุปกรณ์ทั้งหมดได้ด้วย Storage Manager API ในแพลตฟอร์มที่รองรับ Cache Storage API และ IndexedDB ให้การเข้าถึงแบบไม่พร้อมกันไปยังพื้นที่เก็บข้อมูลถาวรสำหรับ PWA และเข้าถึงได้จากเทรดหลัก ผู้ปฏิบัติงานบนเว็บ และ Service Worker ทั้ง 2 อย่างมีบทบาทสำคัญในการทำให้ PWA ทำงานได้อย่างเสถียรเมื่อเครือข่ายไม่น่าเชื่อถือหรือไม่มีอยู่จริง แต่คุณควรใช้แต่ละวิธีเมื่อใด

ใช้ Cache Storage API สำหรับทรัพยากรเครือข่าย ซึ่งเป็นสิ่งที่คุณจะเข้าถึงได้โดยการขอผ่าน URL เช่น HTML, CSS, JavaScript, รูปภาพ, วิดีโอ และเสียง

ใช้ IndexedDB เพื่อจัดเก็บข้อมูลที่มีโครงสร้าง ซึ่งรวมถึงข้อมูลที่ต้องค้นหาได้หรือรวมได้ในลักษณะเดียวกับ NoSQL หรือข้อมูลอื่นๆ เช่น ข้อมูลเฉพาะผู้ใช้ที่ไม่จำเป็นต้องตรงกับคำขอ URL โปรดทราบว่า IndexedDB ไม่ได้ออกแบบมาสำหรับการค้นหาข้อความแบบเต็ม

IndexedDB

หากต้องการใช้ IndexedDB ให้เปิดฐานข้อมูลก่อน การดำเนินการนี้จะสร้างฐานข้อมูลใหม่หากยังไม่มี IndexedDB เป็น API แบบไม่พร้อมกันแต่จะใช้ Callback แทนการส่งคืน Promise ตัวอย่างต่อไปนี้ใช้ไลบรารี IDb ของ Jake Archibald ซึ่งเป็น Wrapper ของ Promise ที่มีขนาดเล็กสำหรับ IndexedDB ไลบรารีตัวช่วยไม่จำเป็นต้องใช้ IndexedDB แต่หากต้องการใช้ไวยากรณ์ Promise ไลบรารี idb ก็เป็นตัวเลือก

ตัวอย่างต่อไปนี้สร้างฐานข้อมูลเพื่อเก็บสูตรอาหาร

การสร้างและเปิดฐานข้อมูล

วิธีเปิดฐานข้อมูล

  1. ใช้ฟังก์ชัน openDB เพื่อสร้างฐานข้อมูล IndexedDB ใหม่ที่ชื่อ cookbook เนื่องจากฐานข้อมูล IndexedDB มีการกำหนดเวอร์ชันไว้แล้ว คุณต้องเพิ่มหมายเลขเวอร์ชันทุกครั้งที่คุณเปลี่ยนแปลงโครงสร้างฐานข้อมูล พารามิเตอร์ที่ 2 คือเวอร์ชันฐานข้อมูล ในตัวอย่างนี้มีค่าเป็น 1
  2. ออบเจ็กต์การเริ่มต้นที่มี Callback upgrade() จะส่งไปยัง 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

ลองมาดูออบเจ็กต์สโตร์ที่เพิ่งสร้างขึ้นกัน หลังจากเพิ่มสูตรอาหารใน Object Store และเปิดเครื่องมือสำหรับนักพัฒนาเว็บในเบราว์เซอร์แบบ Chromium หรือ Web Inspector ใน 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;
}

เมื่อเพิ่มคุกกี้แล้ว สูตรนั้นจะอยู่ในฐานข้อมูลพร้อมกับสูตรอื่นๆ ระบบจะตั้งค่าและเพิ่มค่า ID โดยอัตโนมัติด้วย IndexingDB หากคุณรันโค้ดนี้ 2 ครั้ง คุณจะมีรายการคุกกี้ที่เหมือนกัน 2 รายการ

กำลังดึงข้อมูล

วิธีดึงข้อมูลจาก IndexedDB

  1. เริ่มธุรกรรมและระบุออบเจ็กต์สโตร์หรือร้านค้า และประเภทธุรกรรมที่ไม่บังคับ
  2. โทรหา objectStore() จากธุรกรรมนั้น ตรวจสอบว่าคุณระบุชื่อร้านค้าออบเจ็กต์แล้ว
  3. โทรหา get() ด้วยคีย์ที่ต้องการใช้ โดยค่าเริ่มต้น Store จะใช้คีย์เป็นดัชนี
// 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, พื้นที่เก็บข้อมูลเว็บ และแม้แต่ไฟล์โปรแกรมทำงานของบริการและทรัพยากร Dependency แต่ปริมาณพื้นที่เก็บข้อมูลที่ใช้ได้จะแตกต่างกันไปในแต่ละเบราว์เซอร์ ไม่น่าจะหมด เว็บไซต์สามารถจัดเก็บข้อมูลเป็นเมกะไบต์หรือกิกะไบต์ได้ในบางเบราว์เซอร์ ตัวอย่างเช่น 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 DevTools คุณสามารถดูโควต้าของเว็บไซต์และปริมาณพื้นที่เก็บข้อมูลที่ใช้แยกตามการใช้งานโดยเปิดส่วนพื้นที่เก็บข้อมูลในแท็บแอปพลิเคชัน

เครื่องมือสำหรับนักพัฒนาเว็บใน 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

พื้นที่เก็บข้อมูลบนเว็บ

การรองรับเบราว์เซอร์

  • Chrome: 4.
  • ขอบ: 12.
  • Firefox: 3.5.
  • Safari: 4.

แหล่งที่มา

การเข้าถึงระบบไฟล์

การรองรับเบราว์เซอร์

  • Chrome: 86
  • ขอบ: 86
  • Firefox: 111.
  • Safari: 15.2

แหล่งที่มา

ตัวจัดการพื้นที่เก็บข้อมูล

การรองรับเบราว์เซอร์

  • Chrome: 55.
  • ขอบ: 79
  • Firefox: 57
  • Safari: 15.2

แหล่งที่มา

แหล่งข้อมูล