Để tạo ra trải nghiệm ngoại tuyến chất lượng cao, PWA của bạn cần được quản lý bộ nhớ. Trong chương trình lưu vào bộ nhớ đệm, bạn đã tìm hiểu rằng bộ nhớ đệm là một lựa chọn để lưu dữ liệu trên thiết bị. Trong chương này, chúng tôi sẽ hướng dẫn bạn cách quản lý dữ liệu ngoại tuyến, bao gồm cả tính bền vững, giới hạn của dữ liệu và các công cụ có sẵn.
Bộ nhớ
Bộ nhớ không chỉ dùng cho tệp và tài sản, mà còn có thể bao gồm cả các loại dữ liệu khác. Trên tất cả trình duyệt hỗ trợ PWA, các API sau có sẵn để lưu trữ trên thiết bị:
- IndexedDB: Tuỳ chọn lưu trữ đối tượng NoSQL cho dữ liệu có cấu trúc và blob (dữ liệu nhị phân).
- WebStorage: Một cách để lưu trữ các cặp chuỗi khoá/giá trị, sử dụng bộ nhớ cục bộ hoặc bộ nhớ phiên. Tính năng này không hoạt động trong ngữ cảnh trình chạy dịch vụ. API này có tính đồng bộ nên bạn không nên dùng cho việc lưu trữ dữ liệu phức tạp.
- Dung lượng lưu trữ của bộ nhớ đệm: Như đã đề cập trong Mô-đun lưu vào bộ nhớ đệm.
Bạn có thể quản lý tất cả bộ nhớ thiết bị bằng API Trình quản lý bộ nhớ trên các nền tảng được hỗ trợ. Cache Storage API và IndexedDB cấp quyền truy cập không đồng bộ vào bộ nhớ liên tục cho PWA và có thể được truy cập qua luồng chính, trình chạy web và trình chạy dịch vụ. Cả hai đều đóng vai trò thiết yếu trong việc giúp PWA hoạt động ổn định khi mạng không ổn định hoặc không tồn tại. Nhưng khi nào bạn nên sử dụng từng cấu hình này?
Sử dụng API lưu trữ bộ nhớ đệm cho tài nguyên mạng, những thứ bạn sẽ truy cập bằng cách yêu cầu chúng qua một URL, chẳng hạn như HTML, CSS, JavaScript, hình ảnh, video và âm thanh.
Sử dụng IndexedDB để lưu trữ dữ liệu có cấu trúc. Dữ liệu này bao gồm dữ liệu cần có thể tìm kiếm hoặc kết hợp được theo cách tương tự như NoSQL, hoặc dữ liệu khác (chẳng hạn như dữ liệu người dùng cụ thể) mà không nhất thiết phải khớp với yêu cầu URL. Xin lưu ý rằng IndexedDB không được thiết kế để tìm kiếm văn bản đầy đủ.
IndexedDB
Để sử dụng IndexedDB, trước tiên, hãy mở cơ sở dữ liệu. Thao tác này sẽ tạo một cơ sở dữ liệu mới nếu chưa có.
IndexedDB là một API không đồng bộ, nhưng sẽ thực hiện lệnh gọi lại thay vì trả về một Promise. Ví dụ sau đây sử dụng thư viện idb của Jake Archibald. Đây là một trình bao bọc Promise nhỏ cho IndexedDB. Bạn không bắt buộc phải sử dụng thư viện trợ giúp để sử dụng IndexedDB, nhưng nếu muốn sử dụng cú pháp Promise, bạn có thể chọn thư viện idb
.
Ví dụ sau đây sẽ tạo một cơ sở dữ liệu để lưu giữ các công thức nấu ăn.
Tạo và mở cơ sở dữ liệu
Cách mở cơ sở dữ liệu:
- Sử dụng hàm
openDB
để tạo cơ sở dữ liệu IndexedDB mới có tên làcookbook
. Vì cơ sở dữ liệu IndexedDB được tạo phiên bản, bạn cần tăng số phiên bản mỗi khi thực hiện thay đổi đối với cấu trúc cơ sở dữ liệu. Tham số thứ hai là phiên bản cơ sở dữ liệu. Trong ví dụ này, giá trị được đặt là 1. - Đối tượng khởi tạo chứa lệnh gọi lại
upgrade()
được truyền đếnopenDB()
. Hàm callback được gọi khi cơ sở dữ liệu được cài đặt lần đầu tiên hoặc khi nâng cấp lên phiên bản mới. Hàm này là nơi duy nhất mà bạn có thể thực hiện hành động. Các hành động có thể bao gồm việc tạo các kho lưu trữ đối tượng mới (các cấu trúc mà IndexedDB sử dụng để sắp xếp dữ liệu) hoặc chỉ mục (mà bạn muốn tìm kiếm). Đây cũng là nơi di chuyển dữ liệu sẽ diễn ra. Thường thì hàmupgrade()
chứa câu lệnhswitch
không có câu lệnhbreak
để cho phép từng bước diễn ra theo thứ tự, dựa trên phiên bản cũ của cơ sở dữ liệu.
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');
}
}
});
}
Ví dụ này sẽ tạo một kho đối tượng bên trong cơ sở dữ liệu cookbook
có tên là recipes
, với thuộc tính id
được đặt làm khoá chỉ mục của cửa hàng và tạo một chỉ mục khác có tên là type
, dựa trên thuộc tính type
.
Hãy xem kho đối tượng vừa được tạo. Sau khi thêm công thức vào kho lưu trữ đối tượng và mở Công cụ cho nhà phát triển trên trình duyệt dựa trên Chromium hoặc Trình kiểm tra web trên Safari, bạn sẽ thấy:
Đang thêm dữ liệu
IndexedDB sử dụng các giao dịch. Giao dịch sẽ nhóm các hành động lại với nhau, để chúng diễn ra dưới dạng một đơn vị. Chúng giúp đảm bảo rằng cơ sở dữ liệu luôn ở trạng thái nhất quán. Việc này cũng rất quan trọng nếu bạn có nhiều bản sao ứng dụng đang chạy, để ngăn việc ghi đồng thời vào cùng một dữ liệu. Cách thêm dữ liệu:
- Bắt đầu giao dịch bằng cách đặt
mode
thànhreadwrite
. - Tải kho đối tượng để thêm dữ liệu.
- Gọi
add()
bằng dữ liệu bạn đang tiết kiệm được. Phương thức này nhận dữ liệu dưới dạng từ điển (dưới dạng cặp khoá/giá trị) rồi thêm dữ liệu đó vào kho lưu trữ đối tượng. Từ điển phải có thể sao chép bằng tính năng Sao chép có cấu trúc. Nếu muốn cập nhật một đối tượng hiện có, bạn nên gọi phương thứcput()
.
Giao dịch có lời hứa done
sẽ giải quyết khi giao dịch hoàn tất thành công hoặc bị từ chối kèm theo lỗi giao dịch.
Như tài liệu về thư viện IDB giải thích, nếu bạn đang ghi vào cơ sở dữ liệu, thì tx.done
là tín hiệu cho biết mọi thứ đã được xác nhận thành công vào cơ sở dữ liệu. Tuy nhiên, bạn nên đợi các hoạt động riêng lẻ để có thể thấy mọi lỗi khiến giao dịch không thành công.
// 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;
}
Sau khi bạn đã thêm bánh quy, công thức nấu ăn sẽ có trong cơ sở dữ liệu cùng với các công thức khác. Mã nhận dạng này được tự động đặt và tăng lên theoindexDB. Nếu chạy mã này hai lần, bạn sẽ có hai mục nhập cookie giống hệt nhau.
Đang truy xuất dữ liệu
Dưới đây là cách bạn nhận dữ liệu từ IndexedDB:
- Bắt đầu giao dịch và chỉ định đối tượng lưu trữ hoặc các cửa hàng và loại giao dịch không bắt buộc.
- Gọi
objectStore()
từ giao dịch đó. Hãy đảm bảo rằng bạn chỉ định tên kho đối tượng. - Gọi
get()
bằng khoá bạn muốn nhận. Theo mặc định, cửa hàng sử dụng khoá làm chỉ mục.
// 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]);
}
Trình quản lý bộ nhớ
Việc biết cách quản lý bộ nhớ của PWA đặc biệt quan trọng để lưu trữ và truyền trực tuyến phản hồi mạng một cách chính xác.
Dung lượng lưu trữ được chia sẻ giữa tất cả các lựa chọn lưu trữ, bao gồm Bộ nhớ đệm, IndexedDB, Bộ nhớ trên web và thậm chí cả tệp trình chạy dịch vụ cũng như các phần phụ thuộc của nó.
Tuy nhiên, dung lượng bộ nhớ có sẵn sẽ khác nhau tuỳ theo trình duyệt. Bạn có thể sẽ không dùng hết; các trang web có thể lưu trữ megabyte và thậm chí gigabyte dữ liệu trên một số trình duyệt. Ví dụ: Chrome cho phép trình duyệt sử dụng tới 80% tổng dung lượng ổ đĩa và riêng một máy chủ gốc có thể sử dụng tới 60% tổng dung lượng ổ đĩa. Đối với các trình duyệt hỗ trợ Storage API, bạn có thể biết dung lượng còn trống cho ứng dụng của mình, hạn mức và việc sử dụng ứng dụng.
Ví dụ sau đây sử dụng Storage API để nhận hạn mức và mức sử dụng ước tính, sau đó tính toán tỷ lệ phần trăm đã dùng và số byte còn lại. Xin lưu ý rằng navigator.storage
trả về một thực thể của StorageManager
. Storage
có giao diện riêng, dễ khiến người dùng nhầm lẫn.
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.`);
}
Trong Công cụ của Chromium cho nhà phát triển, bạn có thể xem hạn mức của trang web và dung lượng bộ nhớ được sử dụng chia theo tỷ lệ đang sử dụng, bằng cách mở phần Bộ nhớ trong thẻ Ứng dụng.
Firefox và Safari không cung cấp màn hình tóm tắt để xem tất cả hạn mức và mức sử dụng bộ nhớ cho máy chủ gốc hiện tại.
Khả năng lưu trữ cố định dữ liệu
Bạn có thể yêu cầu trình duyệt cung cấp bộ nhớ liên tục trên các nền tảng tương thích để tránh việc tự động loại bỏ dữ liệu sau khi không hoạt động hoặc do áp lực bộ nhớ. Nếu bạn cho phép, trình duyệt sẽ không bao giờ xoá dữ liệu khỏi bộ nhớ. Biện pháp bảo vệ này bao gồm việc đăng ký trình chạy dịch vụ, cơ sở dữ liệu IndexedDB và các tệp trong bộ nhớ đệm. Xin lưu ý rằng người dùng luôn chịu trách nhiệm về bộ nhớ và họ có thể xoá bộ nhớ bất cứ lúc nào, ngay cả khi trình duyệt đã cấp bộ nhớ liên tục.
Để yêu cầu bộ nhớ liên tục, hãy gọi StorageManager.persist()
. Như trước đây, giao diện StorageManager
cho phép truy cập thông qua thuộc tính navigator.storage
.
async function persistData() {
if (navigator.storage && navigator.storage.persist) {
const result = await navigator.storage.persist();
console.log(`Data persisted: ${result}`);
}
Bạn cũng có thể kiểm tra xem bộ nhớ ổn định đã được cấp trong nguồn gốc hiện tại hay chưa bằng cách gọi StorageManager.persisted()
. Firefox yêu cầu người dùng cấp quyền để sử dụng bộ nhớ liên tục. Các trình duyệt dựa trên Chromium sẽ cho phép hoặc từ chối khả năng lưu trữ cố định dựa trên phương pháp phỏng đoán để xác định tầm quan trọng của nội dung đối với người dùng. Một tiêu chí cho Google Chrome là cài đặt PWA. Nếu người dùng đã cài đặt một biểu tượng cho PWA trong hệ điều hành, thì trình duyệt có thể cấp bộ nhớ liên tục.
Hỗ trợ trình duyệt API
Bộ nhớ trên web
Quyền truy cập vào hệ thống tệp
Trình quản lý bộ nhớ