在上一篇,介紹一下漸進式 Web App(離線) – Part 1的文章中,我們討論了典型的pwa應該是什麼樣子的並且同時也介紹了 server worker。到目前為止,我們已經快取了應用殼。在 index.html
和latet.html
頁面中,我們的應用已經實現了離線載入快取資料。在重複訪問時,它們的載入速度更快。在本教程第一部分的結尾,我們能夠離線載入latest.html
,但在使用者離線時無法顯示獲得動態資料。這次學習我們將:
- 當使用者離線時候顯示在
latest
頁面快取 app的資料 - 利用
localStorage
去儲存 app 的資料 - 當使用者連線到Internet時,替換 app 舊的資料並獲取更新新的資料。
離線儲存
在構建PWA時,需要考慮各種儲存機制:
- IndexedDB:這是一個事務型資料庫系統,用於客戶端儲存資料。IndexedDB允許您儲存和檢索用鍵索引的物件,以便對儲存在其中的資料進行高效能搜尋。IndexedDB暴露了一個非同步API,以避免阻塞DOM的載入。但一些研究表明,在某些情況下,它是阻塞的。使用IndexedDB時我推薦使用一些第三方庫,因為在JavaScript中操縱它可能非常冗長複雜。例如:localForage、idb 和 idb-keyval這些第三方模組都是很好滴。
indexDB在瀏覽器的相容性
-
Cache API:這是儲存URL地址資源的最佳選擇。和Service worker配合是非常好滴。
-
PouchDB:是CouchDB的開源JavaScript資料庫。它使應用程式能夠在本地儲存資料,離線,然後同步與CouchDB和相容的伺服器應用程式時重新上線,保持使用者資料同步,不管他們下一次在哪裡登入。PouchDB支援所有現代瀏覽器,使用IndexedDB引擎失敗的話就降級到WebSQL,對Firefox 29+ (包括 Firefox OS and Firefox for Android), Chrome 30+, Safari 5+, Internet Explorer 10+, Opera 21+, Android 4.0+, iOS 7.1+ 和 Windows Phone 8+等等都是相容的。
-
Web Storage 例如 localStorage:它是同步的,是阻止DOM載入的,在瀏覽器中最多使用5MB, 它有簡單的 api去操作儲存鍵值對資料。
Web Storage 的瀏覽器相容表
- WebSQL:這是瀏覽器的關係型資料庫解決方案。它是已經被廢棄,因此,瀏覽器將來可能不支援它。
根據PouchDB的維護者 Nolan Lawson說,在使用資料庫時,最好問自己這些問題:
- 這個資料庫是在記憶體中還是在磁碟上?(PouchDB, IndexedDB)?
- 什麼需要儲存在磁碟上?應用程式關閉或崩潰時應該儲存哪些資料?
- 需要什麼索引才能執行快速查詢?我可以使用記憶體索引而不是磁碟的嗎?
- 我應該怎樣構造我的記憶體資料相對於我的資料庫資料?我在這兩者之間的對映策略是什麼?
- 我的應用程式的查詢需求是什麼?展現檢視真的需要獲取完整的資料,還是隻需要獲取它所需要的一小部分呢?我可以延遲載入任何東西嗎?
您可以檢視考慮如何選擇資料庫,以便更全面地瞭解主題內容。
廢話少扯,讓我們實現即時載入
在我們的 web app 中,我們將用localStorage
,由於我在本教程前面強調的侷限性,我建議你不要在生產環境中使用localStorage
。我們正在構建的應用程式非常簡單,所以是使用了localStorage
。
開啟你的js/latest.js
檔案,我們更新fetchCommits
方法去儲存從Github API
拉取的資料,儲存在localStorage
。程式碼如下:
function fetchCommits() {
var url = `https://api.github.com/repos/unicodeveloper/resources-i-like/commits`;
fetch(url)
.then(function(fetchResponse){
return fetchResponse.json();
})
.then(function(response) {
console.log("Response from Github", response);
var commitData = {};
for (var i = 0; i < posData.length; i++) {
commitData[posData[i]] = {
message: response[i].commit.message,
author: response[i].commit.author.name,
time: response[i].commit.author.date,
link: response[i].html_url
};
}
localStorage.setItem(`commitData`, JSON.stringify(commitData));
for (var i = 0; i < commitContainer.length; i++) {
container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + response[i].commit.message + "</h4>" +
"<h4> Author: " + response[i].commit.author.name + "</h4>" +
"<h4> Time committed: " + (new Date(response[i].commit.author.date)).toUTCString() + "</h4>" +
"<h4>" + "<a href=`" + response[i].html_url + "`>Click me to see more!</a>" + "</h4>";
}
app.spinner.setAttribute(`hidden`, true); // hide spinner
})
.catch(function (error) {
console.error(error);
});
};
複製程式碼
上面有這段程式碼,在第一頁載入的時候,這些提交的資料就儲存到localStorage
了,現在我們寫另外一個函式去渲染這些localStorage
的資料。程式碼如下:
// Get the commits Data from the Web Storage
function fetchCommitsFromLocalStorage(data) {
var localData = JSON.parse(data);
app.spinner.setAttribute(`hidden`, true); //hide spinner
for (var i = 0; i < commitContainer.length; i++) {
container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + localData[posData[i]].message + "</h4>" +
"<h4> Author: " + localData[posData[i]].author + "</h4>" +
"<h4> Time committed: " + (new Date(localData[posData[i]].time)).toUTCString() + "</h4>" +
"<h4>" + "<a href=`" + localData[posData[i]].link + "`>Click me to see more!</a>" + "</h4>";
}
};
複製程式碼
這段程式碼將資料從本地儲存並將其渲染 dom 節點。
現在我們需要知道,什麼條件去呼叫fetchCommits
函式和fetchCommitsFromLocalStorage
函式。
js/latest.js
程式碼如下
(function() {
`use strict`;
var app = {
spinner: document.querySelector(`.loader`)
};
var container = document.querySelector(`.container`);
var commitContainer = [`.first`, `.second`, `.third`, `.fourth`, `.fifth`];
var posData = [`first`, `second`, `third`, `fourth`, `fifth`];
// Check that localStorage is both supported and available
function storageAvailable(type) {
try {
var storage = window[type],
x = `__storage_test__`;
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return false;
}
}
// Get Commit Data from Github API
function fetchCommits() {
var url = `https://api.github.com/repos/unicodeveloper/resources-i-like/commits`;
fetch(url)
.then(function(fetchResponse){
return fetchResponse.json();
})
.then(function(response) {
console.log("Response from Github", response);
var commitData = {};
for (var i = 0; i < posData.length; i++) {
commitData[posData[i]] = {
message: response[i].commit.message,
author: response[i].commit.author.name,
time: response[i].commit.author.date,
link: response[i].html_url
};
}
localStorage.setItem(`commitData`, JSON.stringify(commitData));
for (var i = 0; i < commitContainer.length; i++) {
container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + response[i].commit.message + "</h4>" +
"<h4> Author: " + response[i].commit.author.name + "</h4>" +
"<h4> Time committed: " + (new Date(response[i].commit.author.date)).toUTCString() + "</h4>" +
"<h4>" + "<a href=`" + response[i].html_url + "`>Click me to see more!</a>" + "</h4>";
}
app.spinner.setAttribute(`hidden`, true); // hide spinner
})
.catch(function (error) {
console.error(error);
});
};
// Get the commits Data from the Web Storage
function fetchCommitsFromLocalStorage(data) {
var localData = JSON.parse(data);
app.spinner.setAttribute(`hidden`, true); //hide spinner
for (var i = 0; i < commitContainer.length; i++) {
container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + localData[posData[i]].message + "</h4>" +
"<h4> Author: " + localData[posData[i]].author + "</h4>" +
"<h4> Time committed: " + (new Date(localData[posData[i]].time)).toUTCString() + "</h4>" +
"<h4>" + "<a href=`" + localData[posData[i]].link + "`>Click me to see more!</a>" + "</h4>";
}
};
if (storageAvailable(`localStorage`)) {
if (localStorage.getItem(`commitData`) === null) {
/* The user is using the app for the first time, or the user has not
* saved any commit data, so show the user some fake data.
*/
fetchCommits();
console.log("Fetch from API");
} else {
fetchCommitsFromLocalStorage(localStorage.getItem(`commitData`));
console.log("Fetch from Local Storage");
}
}
else {
toast("We can`t cache your app data yet..");
}
})();
複製程式碼
在上面的程式碼片斷,我們正在檢查瀏覽器是否支援本地儲存,如果它支援,我們繼續檢查是否已經快取了提交資料。如果沒有被快取,我們將請求資料,顯示到頁面上並且快取請求的資料。
現在,從新重新整理一遍瀏覽器,確保你做了一個清晰的快取,強制重新整理,否則我們不會看到我們的程式碼更改的結果。
現在,離線並載入最新頁面。將發生了什麼事呢?
Yaaay!!! 它載入資料沒有任何問題。
檢視DevTools
,你間看到資料已經被快取到localStorage
當使用者離線時,看看它載入的速度!!!
還有一件事
現在,我們可以立即從本地儲存獲取資料。但是我們如何獲得最新的資料?當使用者線上時,我們需要一種仍然獲得新資料的方法。
so easy, 讓我們新增一個重新整理按鈕,觸發一個請求到GitHub獲得的最新資料。
開啟latest.html
檔案,並且新增一個重新整理按鈕到<header>
標籤
<button id="butRefresh" class="headerButton" aria-label="Refresh"></button>
複製程式碼
新增的按鈕後<header>
標籤應該是這樣的:
<header>
<span class="header__icon">
<svg class="menu__icon no--select" width="24px" height="24px" viewBox="0 0 48 48" fill="#fff">
<path d="M6 36h36v-4H6v4zm0-10h36v-4H6v4zm0-14v4h36v-4H6z"></path>
</svg>
</span>
<span class="header__title no--select">PWA - Commits</span>
<button id="butRefresh" class="headerButton" aria-label="Refresh"></button>
</header>
複製程式碼
最後,讓我們在按鈕上附加一個單擊事件並新增功能。開啟js/latest.js
並且新增如下程式碼:
document.getElementById(`butRefresh`).addEventListener(`click`, function() {
// Get fresh, updated data from GitHub whenever you are clicked
toast(`Fetching latest data...`);
fetchCommits();
console.log("Getting fresh data!!!");
});
複製程式碼
清除快取並重新載入。現在,你的latest.html
頁面看起來應該像這樣:
每當使用者需要最新資料時,他們只需單擊重新整理按鈕即可。
附加:
點選檢視下面連結
上一篇: [譯]介紹一下漸進式 Web App(離線) – Part 1
如果有那個地方翻譯出錯或者失誤,請各位大神不吝賜教,小弟感激不盡
期待下一篇: 介紹一下漸進式 Web App(訊息推送) – Part 3