摘要:提及緩存時,不僅僅是指存儲,還包括瀏覽器內用來保存數據以供離線使用的策略。當請求成功返回時,利用返回的數據更新頁面并緩存返回的數據。這種方案主要應用用戶頻繁手動更新內容的場景,比如用戶的收件箱或者文章內容。我們打算應用的策略。
上一篇文章中,我們成功嘗試使用 service workers。我們也可以在應用中緩存一些資源。這篇文章我們準備了解這些:service workers 以及緩存是如何一起配合給用戶一個完美的離線體驗。
在前一個章節當我們學習如何 debugger 的時候,我們了解到瀏覽器的緩存存儲。提及緩存時,不僅僅是指存儲,還包括瀏覽器內用來保存數據以供離線使用的策略。
在這篇文章中,我們將要:
了解社區中常見的緩存策略
嘗試可用的緩存 api
做一個用來展示 Github trending project 的 demo
在 demo 中演示離線狀態下利用緩存所帶來的體驗
緩存策略軟件工程中的每一個理論都是對同一類問題解決方案的總結,每一個都需要時間整理并被大眾接受,成為推薦的解決方案。對于 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,少量的圖片。
緩存請求返回的數據這個方案是指如果之前的網絡請求數據被緩存了,那么就用緩存的數據更新頁面。如果緩存不可用,那直接去網絡請求數據。當請求成功返回時,利用返回的數據更新頁面并緩存返回的數據。
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; }); }); }) ); });
這種方案主要應用用戶頻繁手動更新內容的場景,比如用戶的收件箱或者文章內容。
先展示緩存,再根據請求的數據更新頁面這種方案將同時請求緩存以及服務端的數據。如果某一項在緩存中有對應的數據,好,直接在頁面中展示。當網絡請求的數據返回時,利用返回的數據更新頁面:
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 });
在大多數情況下,網絡請求返回的數據會將從緩存中取出的數據覆蓋。但在網頁中,什么情況都有可能發生,有時候網絡請求數據比從緩存中取數據要快。因此,我們需要設置一個 flag 來判斷網絡請求有沒有返回,這就是上例中的 networkReturned。
緩存部分技術選型目前有兩種可持續性數據存儲方案 -- Cache Storage 以及 Index DB(IDB)。
Cache Storage:在過去的一段時間里,我們依賴 AppCache 來進行緩存處理,但我們需要一個可操作性更強的 API。幸運的是,瀏覽器提供了 Cache 這樣的一個 API,給 Service Worker 的緩存操作帶來了更多的可能。并且,這個 API 同時支持 service workers 以及 web 頁面。在前一篇文章中,我們已經使用過了這個 API。
Index DB:Index DB 是一個異步數據存儲方案。對于這個 API 是又愛又恨,還好,像localForage這樣的類庫使用類似localStorage的操作方式簡化了API。
Service Worker 對于這兩種存儲方案都提供支持。那么問題來了,什么場景下選擇哪一種技術方案呢? Addy Osmani 的博客已經總結好了。
對于利用 URL 可直接查看的資源,使用支持 Service Worker 的 Cache Storage。其它類型的資源,使用利用 Promise 包裹之后的 IndexedDB。SW Precache
上文已經介紹了緩存策略以及數據緩存數據。在實戰之前,還想給大家介紹一下谷歌的 SW Precache。
這個工具還有一個額外的功能:將我們之前討論的緩存文件設置利用正則簡化成一個配置對象。所有你需要做的就是在一個數組中定義緩存的項目。
讓我們來嘗試使用一下 precache,讓其自動生成 service-worker.js。首先,我們需要在項目的根目錄下新增一個 package.json 文件:
npm init -y
安裝 sw-precache:
npm install --save-dev sw-precache
創建一個配置文件:
// ./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 應用離線功能之前,讓我們先來完成應用的基本功能。
回到 app.js 文件,我們要在頁面加載完成時去獲取當前 Github 流行的項目(項目以 star 數的多少來排序):
(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 字符串中的日期。我們是這樣構造的:
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 幫我們將日期構造成 Github API 所規定的格式(yyyy-mm-dd)。
當 getTrends 獲取數據之后,調用了 updateTrends 方法,傳入獲取到的數據。讓我們看看這個方法做了些什么:
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)); } }
遍歷請求返回的數據,利用 createCard 來創建 DOM 模板,然后,將這段 DOM 插入 .trends 元素:
createCard 利用下面的代碼來創建模板:
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; }
下面就是所創建的 DOM 結構:
Card Titleinfo JavaScript star 299 assessment 100A set of best practices for JavaScript projects
在應用程序運行時,需要緩存從服務端獲取的動態內容。不再是 app shell 了,而是用戶真正瀏覽的內容。
我們需要提前配置告訴 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。我們打算應用“Cache, Then network.”的策略。
這樣,我們先展示緩存的內容,當有網絡連接時候,更新內容:
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
當你運行應用的時候,嘗試刷新,打開控制臺,勾選 offline 選項。之后,刷新,以及見證奇跡的時刻:
刷新用戶可能需要在網絡情況更佳的時候刷新頁面,我們需要給予用戶這樣的權利。我們可以給刷新按鈕添加一個事件,當時間觸發時,調用 getTrends 方法:
document.addEventListener("DOMContentLoaded", function() { app.getTrends() // Event listener for refresh button const refreshButton = document.querySelector(".refresh"); refreshButton.addEventListener("click", app.getTrends) })下一步?
感覺不是很滿足?現在你已經知道了如何創建離線應用,在接下來的文章中,我們將繼續討論這項技術的有趣之處,包括推送通知,主屏幕圖標創建等等···
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93937.html
摘要:上面提到在安卓完全不需要像這樣大費周章的繞彎路,所以安卓可能就不需要這個自定義的,這樣又會導致面臨著與安卓差異化嚴重問題。前言 最早接觸離線包的概念要追溯到16年初,項目迎來大改版,其中重點項目之一就是離線包方案的制定與實施。離線包顧名思義就是將H5/CSS/JS和資源文件打包提前下發到App中,這樣App在加載網頁的時候實際上加載的是本地的文件,減少網絡請求來提高網頁的渲染速度,并實現動態...
摘要:使用離線應用構建應用服務端服務器配置創建文件客戶端構建,并在標簽上添加屬性,屬性值是服務器上配置的緩存資源列表的文件名配置相關事件,創建離線文件內容將狀態代碼轉化成狀態離線應用創建即使沒有互聯網連接也可以使用的應用程序。 HTML5新增了localstroage和application cache做離線緩存,兩種緩存各有應用的場景,今天我們說說application cache這種方案...
摘要:我喜歡移動,而且也是那些堅持使用技術構建移動應用程序的人之一。我們準備做這樣的一個漸進式應用是典型的旨在提高用戶離線體驗的應用。當我們開始構建應用時,你就能理解上面的場景了。的作用范圍是針對相對路徑的。最佳的做法是在應用的入口。 我喜歡移動app,而且也是那些堅持使用Web技術構建移動應用程序的人之一。 經過技術的不斷迭代(可能還有一些其它的東西),移動體驗設計愈來愈平易近人,給予用戶...
閱讀 718·2021-10-14 09:42
閱讀 1971·2021-09-22 15:04
閱讀 1574·2019-08-30 12:44
閱讀 2140·2019-08-29 13:29
閱讀 2734·2019-08-29 12:51
閱讀 547·2019-08-26 18:18
閱讀 702·2019-08-26 13:43
閱讀 2808·2019-08-26 13:38