摘要:提及緩存時,不僅僅是指存儲,還包括瀏覽器內(nèi)用來保存數(shù)據(jù)以供離線使用的策略。當請求成功返回時,利用返回的數(shù)據(jù)更新頁面并緩存返回的數(shù)據(jù)。這種方案主要應(yīng)用用戶頻繁手動更新內(nèi)容的場景,比如用戶的收件箱或者文章內(nèi)容。我們打算應(yīng)用的策略。
上一篇文章中,我們成功嘗試使用 service workers。我們也可以在應(yīng)用中緩存一些資源。這篇文章我們準備了解這些:service workers 以及緩存是如何一起配合給用戶一個完美的離線體驗。
在前一個章節(jié)當我們學(xué)習如何 debugger 的時候,我們了解到瀏覽器的緩存存儲。提及緩存時,不僅僅是指存儲,還包括瀏覽器內(nèi)用來保存數(shù)據(jù)以供離線使用的策略。
在這篇文章中,我們將要:
了解社區(qū)中常見的緩存策略
嘗試可用的緩存 api
做一個用來展示 Github trending project 的 demo
在 demo 中演示離線狀態(tài)下利用緩存所帶來的體驗
緩存策略軟件工程中的每一個理論都是對同一類問題解決方案的總結(jié),每一個都需要時間整理并被大眾接受,成為推薦的解決方案。對于 PWA 的緩存策略來說同樣如此。Jake Archibald 匯總了很多常用的方案,但我們只打算介紹其中一些常用的:
Install 期間緩存這個方案我們在上一篇文章中介紹過,緩存 app shell 展示時需要的所有資源:
self.addEventListener("install", function(e) { console.log("[ServiceWorker] Install"); e.waitUntil( caches.open(cacheName).then(function(cache) { console.log("[ServiceWorker] Caching app shell"); return cache.addAll(filesToCache); }) ); });
緩存的資源包括 HTML 模板,CSS 文件,JavaScript,fonts,少量的圖片。
緩存請求返回的數(shù)據(jù)這個方案是指如果之前的網(wǎng)絡(luò)請求數(shù)據(jù)被緩存了,那么就用緩存的數(shù)據(jù)更新頁面。如果緩存不可用,那直接去網(wǎng)絡(luò)請求數(shù)據(jù)。當請求成功返回時,利用返回的數(shù)據(jù)更新頁面并緩存返回的數(shù)據(jù)。
self.addEventListener("fetch", function(event) { event.respondWith( caches.open(cacheName).then(function(cache) { return cache.match(event.request).then(function (response) { return response || fetch(event.request).then(function(response) { cache.put(event.request, response.clone()); return response; }); }); }) ); });
這種方案主要應(yīng)用用戶頻繁手動更新內(nèi)容的場景,比如用戶的收件箱或者文章內(nèi)容。
先展示緩存,再根據(jù)請求的數(shù)據(jù)更新頁面這種方案將同時請求緩存以及服務(wù)端的數(shù)據(jù)。如果某一項在緩存中有對應(yīng)的數(shù)據(jù),好,直接在頁面中展示。當網(wǎng)絡(luò)請求的數(shù)據(jù)返回時,利用返回的數(shù)據(jù)更新頁面:
let networkReturned = false; if ("caches" in window) { caches.match(app.apiURL).then(function(response) { if (response) { response.json().then(function(trends) { console.log("From cache...") if(!networkReturned) { app.updateTrends(trends); } }); } }); } fetch(app.apiURL) .then(response => response.json()) .then(function(trends) { console.log("From server...") networkReturned = true; app.updateTrends(trends.items) }).catch(function(err) { // Error });
在大多數(shù)情況下,網(wǎng)絡(luò)請求返回的數(shù)據(jù)會將從緩存中取出的數(shù)據(jù)覆蓋。但在網(wǎng)頁中,什么情況都有可能發(fā)生,有時候網(wǎng)絡(luò)請求數(shù)據(jù)比從緩存中取數(shù)據(jù)要快。因此,我們需要設(shè)置一個 flag 來判斷網(wǎng)絡(luò)請求有沒有返回,這就是上例中的 networkReturned。
緩存部分技術(shù)選型目前有兩種可持續(xù)性數(shù)據(jù)存儲方案 -- Cache Storage 以及 Index DB(IDB)。
Cache Storage:在過去的一段時間里,我們依賴 AppCache 來進行緩存處理,但我們需要一個可操作性更強的 API。幸運的是,瀏覽器提供了 Cache 這樣的一個 API,給 Service Worker 的緩存操作帶來了更多的可能。并且,這個 API 同時支持 service workers 以及 web 頁面。在前一篇文章中,我們已經(jīng)使用過了這個 API。
Index DB:Index DB 是一個異步數(shù)據(jù)存儲方案。對于這個 API 是又愛又恨,還好,像localForage這樣的類庫使用類似localStorage的操作方式簡化了API。
Service Worker 對于這兩種存儲方案都提供支持。那么問題來了,什么場景下選擇哪一種技術(shù)方案呢? Addy Osmani 的博客已經(jīng)總結(jié)好了。
對于利用 URL 可直接查看的資源,使用支持 Service Worker 的 Cache Storage。其它類型的資源,使用利用 Promise 包裹之后的 IndexedDB。SW Precache
上文已經(jīng)介紹了緩存策略以及數(shù)據(jù)緩存數(shù)據(jù)。在實戰(zhàn)之前,還想給大家介紹一下谷歌的 SW Precache。
這個工具還有一個額外的功能:將我們之前討論的緩存文件設(shè)置利用正則簡化成一個配置對象。所有你需要做的就是在一個數(shù)組中定義緩存的項目。
讓我們來嘗試使用一下 precache,讓其自動生成 service-worker.js。首先,我們需要在項目的根目錄下新增一個 package.json 文件:
npm init -y
安裝 sw-precache:
npm install --save-dev sw-precache
創(chuàng)建一個配置文件:
// ./tools/precache.js const name = "scotchPWA-v1" module.exports = { staticFileGlobs: [ "./index.html", "./images/*.{png,svg,gif,jpg}", "./fonts/**/*.{woff,woff2}", "./js/*.js", "./css/*.css", "https://fonts.googleapis.com/icon?family=Material+Icons" ], stripPrefix: "." };
staticFileGlobs 里面利用正則匹配我們想要緩存的文件。只需要利用正則,比之前枚舉所有的文件簡單很多。
在 package.json 中新增一個 script 用來生成 service worker 文件:
"scripts": { "sw": "sw-precache --config=tools/precache.js --verbose" },
運行下面的命令即可生成 service worker 文件:
npm run sw
查看生成的文件,是不是很熟悉?
完成 demo在做 web 應(yīng)用離線功能之前,讓我們先來完成應(yīng)用的基本功能。
回到 app.js 文件,我們要在頁面加載完成時去獲取當前 Github 流行的項目(項目以 star 數(shù)的多少來排序):
(function() { const app = { apiURL: `https://api.github.com/search/repositories?q=created:%22${dates.startDate()}+..+${dates.endDate()}%22%20language:javascript&sort=stars&order=desc` } app.getTrends = function() { fetch(app.apiURL) .then(response => response.json()) .then(function(trends) { console.log("From server...") app.updateTrends(trends.items) }).catch(function(err) { // Error }); } document.addEventListener("DOMContentLoaded", function() { app.getTrends() }) if ("serviceWorker" in navigator) { navigator.serviceWorker .register("/service-worker.js") .then(function() { console.log("Service Worker Registered"); }); } })()
注意 API URL 字符串中的日期。我們是這樣構(gòu)造的:
Date.prototype.yyyymmdd = function() { // getMonth is zero based, // so we increment by 1 let mm = this.getMonth() + 1; let dd = this.getDate(); return [this.getFullYear(), (mm>9 ? "" : "0") + mm, (dd>9 ? "" : "0") + dd ].join("-"); }; const dates = { startDate: function() { const startDate = new Date(); startDate.setDate(startDate.getDate() - 7); return startDate.yyyymmdd(); }, endDate: function() { const endDate = new Date(); return endDate.yyyymmdd(); } }
yyyymmdd 幫我們將日期構(gòu)造成 Github API 所規(guī)定的格式(yyyy-mm-dd)。
當 getTrends 獲取數(shù)據(jù)之后,調(diào)用了 updateTrends 方法,傳入獲取到的數(shù)據(jù)。讓我們看看這個方法做了些什么:
app.updateTrends = function(trends) { const trendsRow = document.querySelector(".trends"); for(let i = 0; i < trends.length; i++) { const trend = trends[i]; trendsRow.appendChild(app.createCard(trend)); } }
遍歷請求返回的數(shù)據(jù),利用 createCard 來創(chuàng)建 DOM 模板,然后,將這段 DOM 插入 .trends 元素:
createCard 利用下面的代碼來創(chuàng)建模板:
const app = { apiURL: `...`, cardTemplate: document.querySelector(".card-template") } app.createCard = function(trend) { const card = app.cardTemplate.cloneNode(true); card.classList.remove("card-template") card.querySelector(".card-title").textContent = trend.full_name card.querySelector(".card-lang").textContent = trend.language card.querySelector(".card-stars").textContent = trend.stargazers_count card.querySelector(".card-forks").textContent = trend.forks card.querySelector(".card-link").setAttribute("href", trend.html_url) card.querySelector(".card-link").setAttribute("target", "_blank") return card; }
下面就是所創(chuàng)建的 DOM 結(jié)構(gòu):
Card Titleinfo JavaScript star 299 assessment 100A set of best practices for JavaScript projects
在應(yīng)用程序運行時,需要緩存從服務(wù)端獲取的動態(tài)內(nèi)容。不再是 app shell 了,而是用戶真正瀏覽的內(nèi)容。
我們需要提前配置告訴 service worker ,在運行時需要緩存的文件:
// ./tools/precache.js const name = "scotchPWA-v1" module.exports = { staticFileGlobs: [ // ... ], stripPrefix: ".", // Run time cache runtimeCaching: [{ urlPattern: /https://api.github.com/search/repositories/, handler: "networkFirst", options: { cache: { name: name } } }] };
我們定義了一個 url 正則匹配符,匹配成功時,讀取緩存。這個正則匹配所有的 Github 搜索 API。我們打算應(yīng)用“Cache, Then network.”的策略。
這樣,我們先展示緩存的內(nèi)容,當有網(wǎng)絡(luò)連接時候,更新內(nèi)容:
app.getTrends = function() { const networkReturned = false; if ("caches" in window) { caches.match(app.apiURL).then(function(response) { if (response) { response.json().then(function(trends) { console.log("From cache...") if(!networkReturned) { app.updateTrends(trends); } }); } }); } fetch(app.apiURL) .then(response => response.json()) .then(function(trends) { console.log("From server...") app.updateTrends(trends.items) networkReturned = true; }).catch(function(err) { // Error }); }
在 precache.js 中更新緩存的版本,重新生成 service worker:
const name = "scotchPWA-v2"
npm run sw
當你運行應(yīng)用的時候,嘗試刷新,打開控制臺,勾選 offline 選項。之后,刷新,以及見證奇跡的時刻:
刷新用戶可能需要在網(wǎng)絡(luò)情況更佳的時候刷新頁面,我們需要給予用戶這樣的權(quán)利。我們可以給刷新按鈕添加一個事件,當時間觸發(fā)時,調(diào)用 getTrends 方法:
document.addEventListener("DOMContentLoaded", function() { app.getTrends() // Event listener for refresh button const refreshButton = document.querySelector(".refresh"); refreshButton.addEventListener("click", app.getTrends) })下一步?
感覺不是很滿足?現(xiàn)在你已經(jīng)知道了如何創(chuàng)建離線應(yīng)用,在接下來的文章中,我們將繼續(xù)討論這項技術(shù)的有趣之處,包括推送通知,主屏幕圖標創(chuàng)建等等···
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/93937.html
摘要:上面提到在安卓完全不需要像這樣大費周章的繞彎路,所以安卓可能就不需要這個自定義的,這樣又會導(dǎo)致面臨著與安卓差異化嚴重問題。前言 最早接觸離線包的概念要追溯到16年初,項目迎來大改版,其中重點項目之一就是離線包方案的制定與實施。離線包顧名思義就是將H5/CSS/JS和資源文件打包提前下發(fā)到App中,這樣App在加載網(wǎng)頁的時候?qū)嶋H上加載的是本地的文件,減少網(wǎng)絡(luò)請求來提高網(wǎng)頁的渲染速度,并實現(xiàn)動態(tài)...
摘要:使用離線應(yīng)用構(gòu)建應(yīng)用服務(wù)端服務(wù)器配置創(chuàng)建文件客戶端構(gòu)建,并在標簽上添加屬性,屬性值是服務(wù)器上配置的緩存資源列表的文件名配置相關(guān)事件,創(chuàng)建離線文件內(nèi)容將狀態(tài)代碼轉(zhuǎn)化成狀態(tài)離線應(yīng)用創(chuàng)建即使沒有互聯(lián)網(wǎng)連接也可以使用的應(yīng)用程序。 HTML5新增了localstroage和application cache做離線緩存,兩種緩存各有應(yīng)用的場景,今天我們說說application cache這種方案...
摘要:使用離線應(yīng)用構(gòu)建應(yīng)用服務(wù)端服務(wù)器配置創(chuàng)建文件客戶端構(gòu)建,并在標簽上添加屬性,屬性值是服務(wù)器上配置的緩存資源列表的文件名配置相關(guān)事件,創(chuàng)建離線文件內(nèi)容將狀態(tài)代碼轉(zhuǎn)化成狀態(tài)離線應(yīng)用創(chuàng)建即使沒有互聯(lián)網(wǎng)連接也可以使用的應(yīng)用程序。 HTML5新增了localstroage和application cache做離線緩存,兩種緩存各有應(yīng)用的場景,今天我們說說application cache這種方案...
摘要:我喜歡移動,而且也是那些堅持使用技術(shù)構(gòu)建移動應(yīng)用程序的人之一。我們準備做這樣的一個漸進式應(yīng)用是典型的旨在提高用戶離線體驗的應(yīng)用。當我們開始構(gòu)建應(yīng)用時,你就能理解上面的場景了。的作用范圍是針對相對路徑的。最佳的做法是在應(yīng)用的入口。 我喜歡移動app,而且也是那些堅持使用Web技術(shù)構(gòu)建移動應(yīng)用程序的人之一。 經(jīng)過技術(shù)的不斷迭代(可能還有一些其它的東西),移動體驗設(shè)計愈來愈平易近人,給予用戶...
閱讀 798·2021-10-14 09:42
閱讀 2026·2021-09-22 15:04
閱讀 1669·2019-08-30 12:44
閱讀 2213·2019-08-29 13:29
閱讀 2790·2019-08-29 12:51
閱讀 605·2019-08-26 18:18
閱讀 777·2019-08-26 13:43
閱讀 2873·2019-08-26 13:38