在上一篇,介绍一下渐进式 Web App(离线) - Part 1的文章中,我们讨论了典型的pwa应该是什么样子的并且同时也介绍了 server worker。到目前为止,我们已经缓存了应用壳。在 index.htmllatet.html页面中,我们的应用已经实现了离线加载缓存数据。在重复访问时,它们的加载速度更快。在本教程第一部分的结尾,我们能够离线加载latest.html,但在用户离线时无法显示获得动态数据。这次学习我们将:

  • 当用户离线时候显示在latest页面缓存 app的数据
  • 利用localStorage去存储 app 的数据
  • 当用户连接到Internet时,替换 app 旧的数据并获取更新新的数据。

离线存储

在构建PWA时,需要考虑各种存储机制:

  • IndexedDB:这是一个事务型数据库系统,用于客户端存储数据。IndexedDB允许您存储和检索用键索引的对象,以便对存储在其中的数据进行高性能搜索。IndexedDB暴露了一个异步API,以避免阻塞DOM的加载。但一些研究表明,在某些情况下,它是阻塞的。使用IndexedDB时我推荐使用一些第三方库,因为在JavaScript中操纵它可能非常冗长复杂。例如:localForageidbidb-keyval这些第三方模块都是很好滴。

indexDB在浏览器的兼容性

  • Cache API:这是存储URL地址资源的最佳选择。和Service worker配合是非常好滴。
  • PouchDB:是CouchDB的开源JavaScript数据库。它使应用程序能够在本地存储数据,离线,然后同步与CouchDB和兼容的服务器应用程序时重新上线,保持用户数据同步,不管他们下一次在哪里登录。PouchDB支持所有现代浏览器,使用IndexedDB引擎失败的话就降级到WebSQL,对Firefox 29+ (包括 Firefox OS and Firefox for Android), Chrome 30+, Safari 5+, Internet Explorer 10+, Opera 21+, Android 4.0+, iOS 7.1+ 和 Windows Phone 8+等等都是兼容的。
  • Web Storage 例如 localStorage:它是同步的,是阻止DOM加载的,在浏览器中最多使用5MB, 它有简单的 api去操作存储键值对数据。

Web Storage 的浏览器兼容表

  • WebSQL:这是浏览器的关系型数据库解决方案。它是已经被废弃,因此,浏览器将来可能不支持它。

根据PouchDB的维护者 Nolan Lawson说,在使用数据库时,最好问自己这些问题:

  • 这个数据库是在内存中还是在磁盘上?(PouchDB, IndexedDB)?
  • 什么需要存储在磁盘上?应用程序关闭或崩溃时应该保存哪些数据?
  • 需要什么索引才能执行快速查询?我可以使用内存索引而不是磁盘的吗?
  • 我应该怎样构造我的内存数据相对于我的数据库数据?我在这两者之间的映射策略是什么?
  • 我的应用程序的查询需求是什么?展现视图真的需要获取完整的数据,还是只需要获取它所需要的一小部分呢?我可以延迟加载任何东西吗?

您可以查看考虑如何选择数据库,以便更全面地了解主题内容。

废话少扯,让我们实现即时加载

在我们的 web app 中,我们将用localStorage,由于我在本教程前面强调的局限性,我建议你不要在生产环境中使用localStorage。我们正在构建的应用程序非常简单,所以是使用了localStorage

打开你的js/latest.js文件,我们更新fetchCommits方法去存储从 Github API 拉取的数据,存储在localStorage。代码如下:


function fetchCommits() {
var url = 'https://api.github.com/repos/unicodeveloper/resources-i-like/commits'; fetch(url)
.then(function(fetchResponse){
return fetchResponse.json();
})
.then(function(response) {
console.log("Response from Github", response); var commitData = {}; for (var i = 0; i < posData.length; i++) {
commitData[posData[i]] = {
message: response[i].commit.message,
author: response[i].commit.author.name,
time: response[i].commit.author.date,
link: response[i].html_url
};
} localStorage.setItem('commitData', JSON.stringify(commitData)); for (var i = 0; i < commitContainer.length; i++) { container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + response[i].commit.message + "</h4>" +
"<h4> Author: " + response[i].commit.author.name + "</h4>" +
"<h4> Time committed: " + (new Date(response[i].commit.author.date)).toUTCString() + "</h4>" +
"<h4>" + "<a href='" + response[i].html_url + "'>Click me to see more!</a>" + "</h4>"; } app.spinner.setAttribute('hidden', true); // hide spinner
})
.catch(function (error) {
console.error(error);
});
};

上面有这段代码,在第一页加载的时候,这些提交的数据就存储到localStorage了,现在我们写另外一个函数去渲染这些localStorage的数据。代码如下:


// Get the commits Data from the Web Storage
function fetchCommitsFromLocalStorage(data) {
var localData = JSON.parse(data); app.spinner.setAttribute('hidden', true); //hide spinner for (var i = 0; i < commitContainer.length; i++) { container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + localData[posData[i]].message + "</h4>" +
"<h4> Author: " + localData[posData[i]].author + "</h4>" +
"<h4> Time committed: " + (new Date(localData[posData[i]].time)).toUTCString() + "</h4>" +
"<h4>" + "<a href='" + localData[posData[i]].link + "'>Click me to see more!</a>" + "</h4>"; }
};

这段代码将数据从本地存储并将其渲染 dom 节点。

现在我们需要知道,什么条件去调用fetchCommits函数和fetchCommitsFromLocalStorage函数。

js/latest.js代码如下


(function() {
'use strict'; var app = {
spinner: document.querySelector('.loader')
}; var container = document.querySelector('.container');
var commitContainer = ['.first', '.second', '.third', '.fourth', '.fifth'];
var posData = ['first', 'second', 'third', 'fourth', 'fifth']; // Check that localStorage is both supported and available
function storageAvailable(type) {
try {
var storage = window[type],
x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return false;
}
} // Get Commit Data from Github API
function fetchCommits() {
var url = 'https://api.github.com/repos/unicodeveloper/resources-i-like/commits'; fetch(url)
.then(function(fetchResponse){
return fetchResponse.json();
})
.then(function(response) {
console.log("Response from Github", response); var commitData = {}; for (var i = 0; i < posData.length; i++) {
commitData[posData[i]] = {
message: response[i].commit.message,
author: response[i].commit.author.name,
time: response[i].commit.author.date,
link: response[i].html_url
};
} localStorage.setItem('commitData', JSON.stringify(commitData)); for (var i = 0; i < commitContainer.length; i++) { container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + response[i].commit.message + "</h4>" +
"<h4> Author: " + response[i].commit.author.name + "</h4>" +
"<h4> Time committed: " + (new Date(response[i].commit.author.date)).toUTCString() + "</h4>" +
"<h4>" + "<a href='" + response[i].html_url + "'>Click me to see more!</a>" + "</h4>"; } app.spinner.setAttribute('hidden', true); // hide spinner
})
.catch(function (error) {
console.error(error);
});
}; // Get the commits Data from the Web Storage
function fetchCommitsFromLocalStorage(data) {
var localData = JSON.parse(data); app.spinner.setAttribute('hidden', true); //hide spinner for (var i = 0; i < commitContainer.length; i++) { container.querySelector("" + commitContainer[i]).innerHTML =
"<h4> Message: " + localData[posData[i]].message + "</h4>" +
"<h4> Author: " + localData[posData[i]].author + "</h4>" +
"<h4> Time committed: " + (new Date(localData[posData[i]].time)).toUTCString() + "</h4>" +
"<h4>" + "<a href='" + localData[posData[i]].link + "'>Click me to see more!</a>" + "</h4>"; }
}; if (storageAvailable('localStorage')) {
if (localStorage.getItem('commitData') === null) {
/* The user is using the app for the first time, or the user has not
* saved any commit data, so show the user some fake data.
*/
fetchCommits();
console.log("Fetch from API");
} else {
fetchCommitsFromLocalStorage(localStorage.getItem('commitData'));
console.log("Fetch from Local Storage");
}
}
else {
toast("We can't cache your app data yet..");
}
})();

在上面的代码片断,我们正在检查浏览器是否支持本地存储,如果它支持,我们继续检查是否已经缓存了提交数据。如果没有被缓存,我们将请求数据,显示到页面上并且缓存请求的数据。

现在,从新刷新一遍浏览器,确保你做了一个清晰的缓存,强制刷新,否则我们不会看到我们的代码更改的结果。

现在,离线并加载最新页面。将发生了什么事呢?

Yaaay!!! 它加载数据没有任何问题。

查看DevTools,你间看到数据已经被缓存到localStorage

当用户离线时,看看它加载的速度!!!

还有一件事

现在,我们可以立即从本地存储获取数据。但是我们如何获得最新的数据?当用户在线时,我们需要一种仍然获得新数据的方法。

so easy, 让我们添加一个刷新按钮,触发一个请求到GitHub获得的最新数据。

打开latest.html文件,并且添加一个刷新按钮到<header>标签

<button id="butRefresh" class="headerButton" aria-label="Refresh"></button>

添加的按钮后<header>标签应该是这样的:


<header>
<span class="header__icon">
<svg class="menu__icon no--select" width="24px" height="24px" viewBox="0 0 48 48" fill="#fff">
<path d="M6 36h36v-4H6v4zm0-10h36v-4H6v4zm0-14v4h36v-4H6z"></path>
</svg>
</span>
<span class="header__title no--select">PWA - Commits</span>
<button id="butRefresh" class="headerButton" aria-label="Refresh"></button>
</header>

最后,让我们在按钮上附加一个单击事件并添加功能。打开js/latest.js并且添加如下代码:

document.getElementById('butRefresh').addEventListener('click', function() {
// Get fresh, updated data from GitHub whenever you are clicked
toast('Fetching latest data...');
fetchCommits();
console.log("Getting fresh data!!!");
});

清除缓存并重新加载。现在,你的latest.html页面看起来应该像这样:

每当用户需要最新数据时,他们只需单击刷新按钮即可。

附加:

点击查看下面链接

上一篇: 译介绍一下渐进式 Web App(离线) - Part 1

原文地址

项目代码地址

个人博客地址

如果有那个地方翻译出错或者失误,请各位大神不吝赐教,小弟感激不尽

期待下一篇: 介绍一下渐进式 Web App(消息推送) - Part 3

[译]介绍一下渐进式 Web App(即时加载) - Part 2的更多相关文章

  1. 什么是渐进式Web App(PWA)?为什么值得关注?

    转载自:https://blog.csdn.net/mogoweb/article/details/79029651 在开始PWA这个话题之前,我们先来看看Internet现状. 截至2017年1月, ...

  2. web.xml的加载过程配置详解

      一:web.xml加载过程 简单说一下,web.xml的加载过程.当我们启动一个WEB项目容器时,容器包括(JBoss,Tomcat等).首先会去读取web.xml配置文件里的配置,当这一步骤没有 ...

  3. Java Web应用的加载过程

    在介绍Spring IoC和MVC的加载前,用这篇小文章简单地记录下,最简单的web应用的加载过程. 一.从最简单的web应用出发 使用Eclipse直接创建一个Dynamic Web Project ...

  4. web.xml文件加载顺序

    1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Servl ...

  5. Hibernate之加载策略(延迟加载与即时加载)和抓取策略(fetch)

    假设现在有Book和Category两张表,表的关系为双向的一对多,表结构如下: 假设现在我想查询id为2的那本书的书名,使用session.get(...)方法: Session session=H ...

  6. web.xml 的加载过程

    初始化过程: 在启动Web项目时,容器(比如Tomcat)会读web.xml配置文件中的两个节点<listener>和<contex-param>. 接着容器会创建一个Serv ...

  7. Web Service无法加载协定为“ServiceReference1.xxxxxx”的终结点配置部分,因为找到了该协定的多个终结点配置。请按名称指示首选的终结点配置部分

    Web Service 无法加载协定为“ServiceReference1.xxxxxx”的终结点配置部分,因为找到了该协定的多个终结点配置.请按名称指示首选的终结点配置部分   原因是在web.co ...

  8. web.xml 配置 加载顺序

    web.xml 的加载顺序是:context-param -> listener -> filter -> servlet . 过滤器执行顺序是根据filter-mapping ,不 ...

  9. VS2010 需要缺少的web组件才能加载该项目

    到的问题是解决方案中部分项目无法加载, 提示需要缺少的web组件才能加载该项目,是否通过WEB安装组件来网络安装, 点击确定以后就什么也没有了. 到微软网站去下载Microsoft Web Platf ...

随机推荐

  1. sql 坐标距离排序计算距离(转)

    如果两个坐标的列是(x1,y1).(x2,y2),那么他们之间的距离:SQRT((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2)) sql排序 SELECT * FROM m_store ...

  2. 头次接触wamp服务器、xampp,初次单独使用tomcat部署

    刚刚经过了近两天的接触wamp.xampp.tomcat的时光,真的爽 导师有个网站打不开了,就让我去弄,还有一个网站的后台密码忘了,让我帮忙找回来.我第一感觉就是第一个活不简单,第二个还不简单吗?打 ...

  3. 复杂的Polygon

  4. 被这个C程序折腾死了

    The C programming language 的第13页,1.5.3 行计数的那里,那个统计换行符个数的程序我好像无法运行,无论输入什么,按多少下enter,什么都出不来. #include& ...

  5. js使用心得——避免全局变量冲突的小技巧

    在写js代码的时候,经常会因为这样或者那样的原因用到全局变量,如果全局变量只在一个js里使用,那就没问题,但如果变量在不同的js文件里出现,这时隐藏的问题就会开始暴露,也许你能很快修复出现的BUG,又 ...

  6. 因为AI,所以爱

    作为技术驱动型公司 自我颠覆的核心就是技术上有所突破 2019技术奇点大会上,创始人行在提出 「未来,大数据和人工智能 将成为商业升级的智能发动机」 这与我们的使命不谋而合 时间退回到2016年的冬天 ...

  7. [LC] 117. Populating Next Right Pointers in Each Node II

    Given a binary tree struct Node { int val; Node *left; Node *right; Node *next; } Populate each next ...

  8. python常用包

    今日所得 collections模块 时间模块 random模块 os模块 sys模块 序列化模块 subprocess模块 collections模块 namedtuple:具名元组 #定义方式一: ...

  9. Qt 信号阻塞和断开

    Qt程序中有时候不希望信号槽的触发,在某段流程结束之后,又需要继续回复信号槽状态,这时候可以用阻塞或者断开信号槽的方法来处理. 1. 阻塞方法:bool QObject::blockSignals(b ...

  10. Hadoop什么?

    Hadoop是什么?Hadoop是一个开发和运行处理大规模数据的软件平台,是Appach的一个用java语言实现开源软件框架,实现在大量计算机组成的集群中对海量数据进行分布式计算. Hadoop框架中 ...