漸進式web應用開發---使用indexedDB實現ajax本地資料儲存(四)
在前幾篇文章中,我們使用service worker一步步優化了我們的頁面,現在我們學習使用我們之前的indexedDB, 來快取我們的ajax請求,第一次訪問頁面的時候,我們請求ajax,當我們繼續重新整理頁面的時候,我們從快取裡面去讀取該json資料,想要了解indexedDB,請看這篇文章。
我們下面的demo專案是建立在我們第三篇文章的基礎之上再進行的,想了解之前的文章,請點選這裡.
我們還是按照我們之前的思路來做,首先我們先看看我們整個專案的架構如下:
|----- 專案 | |--- public | | |--- js # 存放所有的js | | | |--- main.js # js入口檔案 | | | |--- store.js | | | |--- myAccount.js | | |--- style # 存放所有的css | | | |--- main.styl # css 入口檔案 | | |--- json # 存放本地模擬資料的json檔案 | | | |--- index.json | | |--- index.html # index.html 頁面 | | |--- images | |--- package.json | |--- webpack.config.js | |--- node_modules | |--- sw.js
public/js/index.json(假如後端介面返回的資料是如下資料) 程式碼如下:
{ "code": 0, "data": [ { "name": "kongzhi111", "age": 28}, { "name": "kongzhi222", "age": 29}, { "name": "kongzhi333", "age": 30} ] }
在我們的 public/js 下新建一個 store.js 檔案,該js檔案的作用是使用indexedDB來快取我們的ajax請求資料的。並且我們需要把該 store.js 檔案存放在我們的 sw.js 中的 CACHE_URLS 中,比如如下所示:
var CACHE_URLS = [ "/public/index.html", // html檔案 "/main.css", // css 樣式表 "/public/images/xxx.jpg", // 圖片 "/main.js", // js 檔案 "/public/js/store.js" ];
然後我們開始編寫我們的 store.js 程式碼,/public/js/store.js 程式碼如下:
import axios from 'axios'; var openDataBase = function() { if (!window.indexedDB) { return false; } // 開啟或建立 store-data 資料庫 var result = window.indexedDB.open('store-data', 2); // 監聽error函式觸發 result.onerror = function(event) { console.log("DataBase error:", event.target.error); } // 監聽當前版本號被升級的時候觸發該函式 result.onupgradeneeded = function(event) { var db = event.target.result; /* 是否包含該物件倉庫名(或叫表名)。如果不包含就建立一個。 該物件中的 keyPath屬性id為主鍵 */ if (!db.objectStoreNames.contains('store')) { db.createObjectStore("store", { keyPath: "id", autoIncrement: true }); } } return result; }; /* @param {storeName} 倉庫名或表名 @param {successCallback} 需要執行的回撥函式 @param {transactionMode} 事務模式 readOnly 只讀,readwrite 可讀可寫 */ var openObjectStore = function(storeName, successCallback, transactionMode) { var db = openDataBase(); if (!db) { return false; } db.onsuccess = function(event) { var targetValue = event.target.result; /* 1. 使用 targetValue.transaction(storeName, transactionMode) 來建立事務 2. 建立事務之後,我們使用 targetValue.transaction(storeName, transactionMode).objectStore(storeName) 這個方法,拿到 IDBObjectStore物件。 */ var objectStore = targetValue.transaction(storeName, transactionMode).objectStore(storeName); successCallback(objectStore); }; return true; }; var getStore = function (successCallback) { var datas = []; var db = openObjectStore("store", function(objectStore) { // 使用流標 objectStore.openCursor() objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; // 如果有流標的話,就把資料放入陣列datas裡面去,依次迴圈存入進去 if (cursor) { datas.push(cursor.value); cursor.continue(); } else { // 否則的話,如果datas有資料的話,就支援呼叫回撥函式 if (datas.length > 0) { successCallback(datas); } else { // 如果datas資料為空,傳送一個json請求 axios.get("http://localhost:8081/public/json/index.json").then(datas => { var list = datas.data.data; // 開啟資料倉庫或表名,執行對應的事務操作 openObjectStore("store", function(datasStore) { for (let i = 0; i < list.length; i++) { datasStore.add(list[i]); } successCallback(datas); }, "readwrite"); }); } } } }); if (!db) { axios.get("http://localhost:8081/public/json/index.json", successCallback); } }; window.getStore = getStore;
如上程式碼,有三個函式,分別為 openDataBase、openObjectStore、及 getStore, 那麼第一個函式 openDataBase() 會開啟一個新的資料庫請求,該函式程式碼內部,首先會判斷瀏覽器是否支援 window.indexedDB ,如果不支援的話,直接返回,然後接著我們建立了一個 store-data 資料庫,並且監聽了 onerror, onupgradeneeded 事件,最後我們建立了一個 store 倉庫名或叫表名。並且以id作為主鍵,並且設定了 autoIncrement 為true,自動增長。然後我們返回了該 result。因為我們的 onsuccess事件並沒有在該方法中監聽。在第二個函式 openObjectStore 中,我們會呼叫 建立資料庫的這個函式,並且去監聽 onsuccess這個事件。
openObjectStore() 該函式會在物件上開啟一個事務,並且在其執行函式,該方法中的第一個引數為 倉庫名稱,第二個引數為開啟倉庫後成功的回撥函式,第三個引數是可選引數,它的含義是事務的型別,有 readonly(只讀) 或 readwrite(可讀可寫),如程式碼:
db.onsuccess = function(event) { var targetValue = event.target.result; /* 1. 使用 targetValue.transaction(storeName, transactionMode) 來建立事務 2. 建立事務之後,我們使用 targetValue.transaction(storeName, transactionMode).objectStore(storeName) 這個方法,拿到 IDBObjectStore物件。 */ var objectStore = targetValue.transaction(storeName, transactionMode).objectStore(storeName); successCallback(objectStore); };
首先我們使用該程式碼:使用 targetValue.transaction(storeName, transactionMode) 來建立事務,然後建立事務完成後,然後我們使用
targetValue.transaction(storeName, transactionMode).objectStore(storeName);
這個方法,拿到 IDBObjectStore物件。然後把該物件 傳入 successCallback 函式內部,在該回調函式中,我們可以使用 objectStore 來增加資料。
getStore(): 該函式接收一個successCallback引數,指回調函式,在程式碼內部,我們首先會建立一個事務,如下程式碼:
var db = openObjectStore("store", function(objectStore) { }
然後我們就會建立流標,並且對所有資料進行迭代,且監聽onsuccess函式,如下程式碼:
var db = openObjectStore("store", function(objectStore) { // 使用流標 objectStore.openCursor() objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; // 如果有流標的話,就把資料放入陣列datas裡面去,依次迴圈存入進去 if (cursor) { datas.push(cursor.value); cursor.continue(); } else { // 否則的話,如果datas有資料的話,就支援呼叫回撥函式 if (datas.length > 0) { successCallback(datas); } else { // 如果datas資料為空,傳送一個json請求 axios.get("http://localhost:8081/public/json/index.json").then(datas => { var list = datas.data.data; // 開啟資料倉庫或表名,執行對應的事務操作 openObjectStore("store", function(datasStore) { for (let i = 0; i < list.length; i++) { datasStore.add(list[i]); } successCallback(datas); }, "readwrite"); }); } } } }
如上程式碼,如果有流標的話,在流標每次前進到一個新的記錄時都會被呼叫,甚至在流標通過最後一條記錄之後也會被呼叫,它是通過 continue 來對內部進行迴圈呼叫,當到最後一條記錄的時候,它後面就沒有資料了,因此就會進入 else 語句內部。因此首先會判斷datas 是否有資料,如果有資料的話,就會呼叫 successCallback(datas); 這句程式碼,把資料datas作為引數傳回給 successCallback 回撥函式,否則的話,如果datas為空的話,我們就會去請求我們本地的json請求,傳送ajax請求,然後把請求的資料,存入到 store倉庫名中,依次迴圈完成後,我們再呼叫 successCallback方法,把資料datas作為引數傳遞出去。當然在我們的 第三個函式 getStore函式中,如果不支援window.indexedDB的話,那麼該瀏覽器的話,我們直接去請求ajax, 如getStore最後一句程式碼:
if (!db) { axios.get("http://localhost:8081/public/json/index.json", successCallback); }
最後再我們的store.js 中,我們會使用 window.getStore = getStore; 讓其成為全域性的。然後在我們的 /public/js/myAccount.js 程式碼如下,就可以呼叫我們的 store.js 中的 getStore方法了,如下程式碼所示:
import $ from 'jquery'; $(function() { // 請求資料並且渲染資料 requestAndRenderFunc(); // 向伺服器請求資料,並且渲染頁面 function requestAndRenderFunc () { getStore(renderHTMLFunc); }; function renderHTMLFunc(datas) { console.log(datas); } });
如上是所有優化後的程式碼,使用indexedDB來儲存我們的資料。因此我們來測試下頁面,我們首先清空下我們瀏覽器的快取資料,然後我們第一次訪問下我們的頁面,我們可以看到我們的網路上,顯示如下請求:
然後我們在控制檯中會看到返回的資料,如下圖所示:
如上我們第一次請求的時候,我們可以看到會請求ajax,然後會返回內容,現在我們繼續重新整理我們的頁面,可以看到如下請求,如下所示:
然後再看我們的控制檯列印如下所示:
我們可以看到我們ajax請求並沒有發請求,但是我們依然可以拿到資料,這是為什麼呢?這是因為我們使用 indexedDB快取ajax資料到本地,因此當我們第二次以後請求的時候,我們拿的都是 indexedDB裡面的資料,我們並沒有發ajax請求,所以使用該訪問,哪怕以後訪問我們的頁面,即使沒有網路的情況下,我們依然可以拿到資料,並且更快載入我們的頁面。我們再來看下我們的 indexedDB儲存的資料如下所示:
如上程式碼我們已經實現了使用indexedDB對資料快取了,並且使用 indexedDB快取裡面的資料了,但是現在有一個新的問題,並且使用者點選一個查詢按鈕,但是查詢按鈕的條件發生改變了,因此ajax請求返回的資料也是根據頁面中查詢的條件來返回的,因此這個時候我們就不能一直使用 indexedDB中的資料了,我們需要重新請求頁面的資料,因此我們需要在我們的 store.js 新增如下程式碼了:
var addToObjectStore = function(storeName, object) { openObjectStore(storeName, function(store) { store.add(object); }, "readwrite"); }; var updateInObjectStore = function(storeName, id, object) { openObjectStore(storeName, function(objectStore) { objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (!cursor) { return; } if (cursor.value.id === id) { objectStore.put(object); return; } cursor.continue(); } }, "readwrite"); } window.addToObjectStore = addToObjectStore; window.updateInObjectStore = updateInObjectStore;
如上 addToObjectStore 函式接收物件儲存的名稱以及要放進儲存的新物件作為引數,該函式我們可以使用如下方式來進行呼叫:
addToObjectStore("store", { id: 1 });
第二個函式 updateInObjectStore 接收物件儲存的名稱,找到與給定的id引數匹配的id物件,並且用它來更新物件,這是通過在物件儲存上開啟 readwrite事務,並且使用流標進行迭代來完成的。在流標到達最後一條記錄或匹配成功之前,函式會一直迭代。如果找到匹配項,就會通過 objectStore.put(object); 來進行更新,此時函式就會通過return返回回來,因為一旦找到匹配,就不需要繼續迭代下一條記錄了。該函式可以如下呼叫:
updateInObjectStore("store", 1, {"id": 1, name: 'kongzhi', age: 30 });
因此為了演示下,我們需要把我們的index.html 程式碼變成如下了:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>service worker 實列</title> </head> <body> <div id="app">222226666</div> <img src="/public/images/xxx.jpg" /> <div style="cursor: pointer;color:red;font-size:18px;padding:5px;border:1px solid #333;" id="submit">點選我新增</div> <div style="cursor: pointer;color:red;font-size:18px;padding:5px;border:1px solid #333;" id="update">點選我修改</div> </body> </html>
如上程式碼,我們新增了 id = "submit" div元素,和 id = "update" 的元素,然後需要在我們的myAccount.js 程式碼新增如下:
function updateDisplay(d) { console.log(d); }; function renderHTMLFunc(datas) { console.log(datas); } var addStore = function(id, name, age) { var obj = { id: id, name: name, age: age }; addToObjectStore("store", obj); renderHTMLFunc(obj); $.getJSON("http://localhost:8081/public/json/index.json", obj, function(data) { updateDisplay(data); }); }; $("#submit").click(function(e) { addStore(3, 'longen1', '111'); }); $("#update").click(function(e) { $.getJSON("http://localhost:8081/public/json/index.json", {id: 1}, function(data) { updateInObjectStore("store", 1, data); updateDisplay(data); }); });
如上程式碼,當我們點選 id 為 submit的元素的時候,我們會呼叫 addStore 函式,在該函式內部會根據 id, name, age 引數來新增一條資料,然後會呼叫 addToObjectStore 函式,先把資料新增到本地儲存裡面去,然後在渲染頁面 呼叫 renderHTMLFunc 函式,最後使用 $.getJSON 請求一條資料,然後把最新的資料渲染到頁面上去。
同樣的道理,update資料的時候,我們會發ajax請求,無論伺服器端是否返回了新的資料,我們都會呼叫 updateInObjectStore 這個函式來更新我們本地的資料。這樣就實現了,如果是狀態傳送改變的話,那麼本地indexedDB儲存的資料庫也會重新得到更新。
我們可以在我們的專案點選下就可以看到效果了。我們點選新增一條資料後,在我們的 indexedDB中看到資訊如下:
如上我們這邊沒有把 data裡面的資料抽離出來,直接把一整個資料直接新增進去了,反正就是這個意思,新增的時候,重新能更新我們的indexedDB裡面的資料,同理我們update修改資料的時候,我們也一樣可以修改我們的某一條資料的。
github原始碼檢視