Để xây dựng trải nghiệm ngoại tuyến vững chắc, PWA của bạn cần có tính năng quản lý bộ nhớ. Trong chương về việc 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ả dữ liệu cố định, giới hạn và các công cụ hiện có.
Bộ nhớ
Bộ nhớ không chỉ là tệp và tài sản, mà còn có thể bao gồm các loại dữ liệu khác. Trên tất cả trình duyệt hỗ trợ PWA, bạn có thể sử dụng các API sau đây cho bộ nhớ trên thiết bị:
- IndexedDB: Một 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 có trong ngữ cảnh của trình chạy dịch vụ. API này là đồng bộ nên không nên dùng để lưu trữ dữ liệu phức tạp.
- 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ợ. API Bộ nhớ đệm và IndexedDB cung cấp quyền truy cập không đồng bộ vào bộ nhớ cố định cho PWA và có thể truy cập được từ 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 có. Nhưng bạn nên sử dụng loại nào khi nào?
Sử dụng API bộ nhớ đệm cho các tài nguyên mạng, những nội dung mà bạn truy cập bằng cách yêu cầu qua 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 tìm kiếm hoặc kết hợp theo cách tương tự như NoSQL, hoặc dữ liệu khác như dữ liệu dành riêng cho người dùng 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 toàn bộ văn bản.
IndexedDB
Để sử dụng IndexedDB, trước tiên, hãy mở một 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ó cơ sở dữ liệu.
IndexedDB là một API không đồng bộ, nhưng nó sẽ thực hiện lệnh gọi lại thay vì trả về một Lời hứa. 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ể sử dụng thư viện idb
.
Ví dụ sau đây tạo một cơ sở dữ liệu để lưu trữ 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 một 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, nên bạn cần tăng số phiên bản mỗi khi thay đổ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 thành 1. - Một đối tượng khởi chạy chứa lệnh gọi lại
upgrade()
được truyền đếnopenDB()
. Hàm gọi lại được gọi khi cài đặt cơ sở dữ liệu 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 có thể xảy ra hành động. Các hành động có thể bao gồm việc tạo kho đối tượng mới (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ễn ra quá trình di chuyển dữ liệu. Thông thường, hàmupgrade()
chứa câu lệnhswitch
mà không có câu lệnhbreak
để cho phép mỗi 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 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 kho 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 đối tượng và mở DevTools trên các trình duyệt dựa trên Chromium hoặc Web Inspector trên Safari, bạn sẽ thấy nội dung sau:
Thêm dữ liệu
IndexedDB sử dụng các giao dịch. Các giao dịch nhóm các hành động lại với nhau, vì vậy, các hành động này xảy ra dưới dạng một đơn vị. Các hàm này giúp đảm bảo cơ sở dữ liệu luôn ở trạng thái nhất quán. Các khoá 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 một giao dịch với
mode
được đặt thànhreadwrite
. - Lấy kho đối tượng, nơi bạn sẽ thêm dữ liệu.
- Gọi
add()
bằng dữ liệu bạn đang lưu. Phương thức này nhận dữ liệu ở dạng từ điển (dưới dạng cặp khoá/giá trị) và thêm dữ liệu đó vào kho đối tượng. Bạn phải có thể sao chép từ điển bằng cách sử dụ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 sẽ gọi phương thứcput()
.
Giao dịch có một lời hứa done
sẽ giải quyết khi giao dịch hoàn tất thành công hoặc từ chối bằng 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, tx.done
là tín hiệu cho biết mọi thứ đã được cam kết thành công vào cơ sở dữ liệu. Tuy nhiên, bạn nên chờ các thao tác riêng lẻ để có thể xem 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ày sẽ nằm trong cơ sở dữ liệu cùng với các công thức khác. Mã nhận dạng được indexedDB tự động đặt và tăng lên. 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.
Truy xuất dữ liệu
Sau đây là cách bạn lấy dữ liệu từ IndexedDB:
- Bắt đầu một giao dịch và chỉ định kho đối tượng hoặc các kho đối tượng, cũng như loại giao dịch (không bắt buộc).
- Gọi
objectStore()
từ giao dịch đó. Hãy nhớ chỉ định tên kho đối tượng. - Gọi
get()
bằng khoá bạn muốn nhận. Theo mặc định, kho lưu trữ 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 bộ nhớ được chia sẻ giữa tất cả các tuỳ chọn bộ nhớ, bao gồm Bộ nhớ đệm, IndexedDB, Bộ nhớ web và thậm chí cả tệp worker và các phần phụ thuộc của tệp đó.
Tuy nhiên, dung lượng bộ nhớ có sẵn sẽ khác nhau tuỳ theo trình duyệt. Bạn không có khả năng sẽ hết bộ nhớ đệm; các trang web có thể lưu trữ hàng megabyte và thậm chí hàng 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 đa 80% tổng dung lượng ổ đĩa và một nguồn gốc riêng lẻ có thể sử dụng tối đa 60% toàn bộ dung lượng ổ đĩa. Đối với các trình duyệt hỗ trợ API bộ nhớ, bạn có thể biết dung lượng bộ nhớ còn lại cho ứng dụng, hạn mức và mức sử dụng.
Ví dụ sau sử dụng API bộ nhớ để lấy hạn mức và mức sử dụng ước tính, sau đó tính tỷ lệ phần trăm đã sử 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
. Có một giao diện Storage
riêng biệt và dễ nhầm lẫn với các giao diện khác.
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ụ phát triển Chromium, bạn có thể xem hạn mức của trang web và mức sử dụng bộ nhớ được phân tích theo nội dung đang sử dụng bộ nhớ đó 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 nguồn 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 lưu trữ liên tục trên các nền tảng tương thích để tránh việc dữ liệu bị xoá tự động sau khi không hoạt động hoặc khi bộ nhớ bị áp lực. Nếu được cấp, trình duyệt sẽ không bao giờ xoá dữ liệu khỏi bộ nhớ. Tính năng bảo vệ này bao gồm cả việc đăng ký worker 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à có thể xoá bộ nhớ bất cứ lúc nào, ngay cả khi trình duyệt đã cấp bộ nhớ cố định.
Để yêu cầu bộ nhớ ổn định, hãy gọi StorageManager.persist()
. Giống như trước đây, giao diện StorageManager
được 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 quyền truy cập bộ nhớ cố đị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ớ cố định. Các trình duyệt dựa trên Chromium cấp hoặc từ chối trạng thái liên tục dựa trên một 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. Ví dụ: một tiêu chí cho Google Chrome là khả năng cài đặt PWA. Nếu người dùng đã cài đặt biểu tượng cho PWA trong hệ điều hành, thì trình duyệt có thể cấp bộ nhớ cố định.
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ớ