漸進式web應用開發---使用indexedDB實現ajax本地數據存儲(四)

在前幾篇文章中,咱們使用service worker一步步優化了咱們的頁面,如今咱們學習使用咱們以前的indexedDB, 來緩存咱們的ajax請求,第一次訪問頁面的時候,咱們請求ajax,當咱們繼續刷新頁面的時候,咱們從緩存裏面去讀取該json數據,想要了解indexedDB,請看這篇文章
咱們下面的demo項目是創建在咱們第三篇文章的基礎之上再進行的,想了解以前的文章,請點擊這裏.css

咱們仍是按照咱們以前的思路來作,首先咱們先看看咱們整個項目的架構以下:html

|----- 項目
|  |--- 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(假如後端接口返回的數據是以下數據) 代碼以下:node

{
  "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 中,好比以下所示:jquery

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 代碼以下:webpack

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(可讀可寫),如代碼:ios

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) 來建立事務,而後建立事務完成後,而後咱們使用git

targetValue.transaction(storeName, transactionMode).objectStore(storeName);
這個方法,拿到 IDBObjectStore對象。而後把該對象 傳入 successCallback 函數內部,在該回調函數中,咱們可使用 objectStore 來增長數據。github

getStore(): 該函數接收一個successCallback參數,指回調函數,在代碼內部,咱們首先會建立一個事務,以下代碼:web

var db = openObjectStore("store", function(objectStore) {
  
}

而後咱們就會建立流標,而且對全部數據進行迭代,且監聽onsuccess函數,以下代碼:ajax

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源碼查看demo

相關文章
相關標籤/搜索