• 渐进式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源码查看demo

  • 相关阅读:
    ArrayList用法
    MessageBox
    将文本文件导入Sql数据库
    在桌面和菜单中添加快捷方式
    泡沫排序
    Making use of localized variables in javascript.
    Remove double empty lines in Visual Studio 2012
    Using Operations Manager Connectors
    Clear SharePoint Designer cache
    Programmatically set navigation settings in SharePoint 2013
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/11222017.html
Copyright © 2020-2023  润新知