使用 React.js 的渐进式 Web 应用程序:第 3 部分 - 离线支持和网络恢复能力

简介: 本文讲的是使用 React.js 的渐进式 Web 应用程序:第 3 部分 - 离线支持和网络恢复能力,一个好的渐进式 Web 应用,不论网络状况如何都能立即加载,并且在不需要网络请求的情况下也能展示 UI (即离线时)。
本文讲的是使用 React.js 的渐进式 Web 应用程序:第 3 部分 - 离线支持和网络恢复能力,

本期是新系列的第三部分,将介绍使用 Lighthouse 优化移动 web 应用传输的技巧。 并看看如何使你的 React 应用离线工作。

一个好的渐进式 Web 应用,不论网络状况如何都能立即加载,并且在不需要网络请求的情况下也能展示 UI (即离线时)。

再次访问 Housing.com 渐进式 Web 应用(使用 React 和 Redux 构建)能够立即加载离线缓存的 UI。

我们可以用 Service Worker 实现这一需求。Service Worker 是一个后台 worker,可以看做是可编程的代理,允许开发者控制 request 执行其他操作。使用 Service Worker,React 应用得以(部分或全部)离线工作。

你能够掌控离线时 UX 的可用程度。你可以只离线缓存应用的外壳,全部数据(就像 ReactHN 缓存 stories 一样),或者像 Housing.com 和 Flipkart 那样,提供有限但有帮助的静态旧数据。并且均通过置灰 UI 蒙层来暗示已离线,这样就能够感知“实时”价格还未同步。

Service worker 实际上依赖两个 API:Fetch (通过网络重新获取内容的标准方式) 和 Cache(应用数据的内容存储,此缓存独立于浏览器缓存和网络状态)。

注意:Service worker 能够应用于渐进式增强。尽管浏览器支持程度还有待提升,但只要网络畅通,不支持此特性的用户也能充分体验 PWA (渐进式 Web 应用程序)。

高级特性基础

Service worker 也设计作为基础 API,让 web 应用更像 native 应用。具体包括:

  • 推送 API - 启用 web 应用消息推送服务。服务器能够任意发送消息,即使 web 应用或浏览器不在工作状态。
  • 后台同步 - 延迟处理直到用户网络连接稳定为止。这能方便保证用户消息的正确发送。应用下次在线时能够启动自动定期更新。

Service Worker 生命周期

每个 Service Worker 的生命周期有三步:注册,安装和激活。Jake Archibald 的这篇文章有更详细的说明

注册

如果要安装 Service Worker,你需要在脚本里注册它。注册后会通知浏览器定位你的 Service Worker 文件,并启动后台安装。在 index.html 中的基本注册方法如下:

// Check for browser support of service worker
if ('serviceWorker' in navigator) {

 navigator.serviceWorker.register('service-worker.js')
 .then(function(registration) {
   // Successful registration
   console.log('Hooray. Registration successful, scope is:', registration.scope);
 }).catch(function(err) {
   // Failed registration, service worker won’t be installed
   console.log('Whoops. Service worker registration failed, error:', error);
 });

}

使用 navigator.serviceWorker.register 注册,注册成功后返回一个 resolve 状态的 Promise 对象。作用域是 registration.scope。

作用域

Service Worker 的作用域由拦截请求的路径决定。默认作用域是 Service Worker 文件所在路径。如果 service-worker.js 在根目录下,则 Service Worker 将控制该域名下所有文件的访问请求。你可以通过在注册时传入其他参数来改变作用域。

navigator.serviceWorker.register('service-worker.js', {
 scope: '/app/'
});

安装和激活

Service workers 是事件驱动的。安装和激活方法由对应的安装和激活事件触发,由 Service Worker 响应。

Service Worker 注册之后,用户第一次访问 PWA 时,install 事件触发,此时确定页面需要缓存的静态资源。当 Service Worker 被认为是的时才会触发该事件,即要么是页面第一次加载 Service Worker 文件,要么是当前文件与之前安装的文件不同,哪怕是一个字节不同,都会被认为是新的。如果你想在有机会控制客户端之前缓存东西,那么 install 是关键所在。

我们可以使用以下代码为静态应用添加最基本的缓存:

var CACHE_NAME = 'my-pwa-cache-v1';
var urlsToCache = [
  '/',
  '/styles/styles.css',
  '/script/webpack-bundle.js'
];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        // Open a cache and cache our files
        return cache.addAll(urlsToCache);
      })
  );
});

addAll() 传入一个 URL 数组,请求并获取文件,然后添加到缓存中去。如果任一步骤获取/写入失败,整个操作失败,并且缓存回退到它的上一个状态。

拦截和缓存请求

当 Service Worker 控制页面时,它能够拦截页面发起的每个请求,并且决定如何处理。这使得它有点像后台代理。我们用它来拦截到 urlsToCache 列表的请求,接着返回资源的本地版本,而不是走网络获取资源。这通过在 fetch 事件上绑定处理方法实现:

self.addEventListener('fetch', function(event) {
    console.log(event.request.url);
    event.respondWith(
        caches.match(event.request).then(function(response) {
            return response || fetch(event.request);
        })
    );
});

在 fetch 监听器中(具体的说是 event.respondWith),向 caches.match() 方法传入一个 promise 对象,这个能够监听请求和从 Service Worker 创建的条目中发现缓存。如果有匹配的缓存响应,返回对应的值。

这就是 Service Worker。以下是学习 Service Worker 可用的免费资源。

如果第三方 API 想要部署他们自己的 Service Worker 来处理其他域传来的请求,Foreign Fetch 可以帮忙。这对于网络化逻辑自定义和单个缓存实例响应定义都有帮助。

探索 - 自定义离线页面

基于 React 的 mobile.twitter.com 用 Service Worker 在网络不可达时提供自定义离线页面。

为用户提供有意义的离线体验(例如:可读内容)是一个很好的目标。也就是说,在早期的 Service Worker 实验中,你会发现设置自定义离线页面是很小但正确的决定。这里有许多优秀的 案例 展示如何实现它。

Lighthouse

如果你的应用在离线时有充分的用户体验,在遇到 Lighthouse 检测的如下条件时,就会全部通过。

start_url 便于检查用户从主界面打开 PWA 时使用离线缓存的体验情况,这项检查能够发现许多的问题,所以要确保 start_url 在你的 Web 应用的 manifest 中。

Chrome 开发工具

开发工具通过应用选项卡支持 「调试 Service Worker」 和 「模拟脱机连通性」。

强烈推荐使用 3G 节流(和 Timeline 面板的 CPU 节流)开发,模拟低端硬件上应用在脱机和网络差的情况下的表现。

应用外壳架构

应用程序外壳(或者应用外壳)架构是构建可靠的和在客户机立即加载的渐进式 Web 应用的一个方法,与 native 应用类似。

应用“外壳” 是最小化的 HTML,CSS 和 JavaScript,要求为用户接口赋能(想想 toolbars,drawers 等等),确保用户重复访问时即时可靠的性能表现。这意味着应用程序外壳不需要每次都下载,只需要网络获取少量必要内容即可。

Housing.com 使用了内容占位符的应用外壳。一旦全部下载完成,立即填充占位,此举有助于提升感官性能。

对于富 JavaScript 架构的 单页应用 来说,应用外壳是首选方法。这个方法依赖外壳的缓存(利用 Service Worker)来运行程序。其次,用 JavaScript 加载每个页面的动态内容。在无网络情况下,应用外壳有助于更快的获取屏幕的起始 HTML 页面。外壳可以使用 Material UI 或是自定义风格。

注意:参考 第一个渐进式 Web 应用 学习设计和实现第一个应用外壳程序,以天气应用为样例。用应用外壳模型实现立即加载 同样探讨了这个模式。

我们利用 Cache Storage API(通过 Service Worker)离线缓存外壳,目的是当重复访问时,应用外壳能够立即加载,这样就能在无网络情况下快速获取屏幕信息,即使内容最终还是来自网络。

记住你可以使用更简单的 SSR 或者 SPA 架构开发 PWA,但它没有同样的性能优势并且更依赖全页缓存。

利用 Service Worker 启动低成本缓存

这里列举两个用于不同离线场景的库:sw-precache 会自动事先缓存静态资源,sw-toolbox 处理运行时缓存以及回退策略。这两个库一起使用能达到互补的效果,需要提供静态内容外壳的性能策略时,总是从缓存中直接获取,而动态的或远程的资源则通过网络请求提供,需要时回退到缓存或静态响应里。

应用外壳缓存:静态资源(HTML, JavaScript, CSS 和 images)提供 web 应用的核心外壳。Sw-precache 确保绝大多数这类静态资源都被缓存下来,并且保持更新。预缓存一个网站离线工作需要的所有资源显然是不现实的。

运行时缓存:一些过于庞大或者很少使用的资源,还有一些动态资源,像来自远程 API 或服务的响应。没有预缓存的请求并不一定要响应网络错误。sw-toolbox 让我们得以灵活实现请求的处理,这能够处理某些资源的运行时缓存和其他资源的自定义回退。

sw-toolbox 支持大多数不同缓存策略,包括网络优先(确保可用数据是最新的,而不是读取缓存),缓存优先(匹配请求与缓存列表,如果资源不存在则发起网络请求),速度优先(同时从缓存和网络请求资源,响应最快的返回结果)。了解这些方法的 优劣 十分重要。

许多网站都在各自的渐进式 Web 应用里利用 sw-toolbox 和 sw-precache 进行离线缓存,例如 Housing.com,the NFL,Flipkart,Alibaba,the Washington Post 等等。也就是说,我们能够一直关注反馈和优化方案。

React app 中的离线缓存

利用 Service Worker 和 Cache Storage API 缓存 URL 的可访问内容能够通过以下这些不同的方式:

了解使用这些 SW 库构建一个 React 应用的讨论也是大有裨益的:

sw-precache 对比 offline-plugin

正如上文提到,offline-plugin 是另一个库,用于添加 Service Worker 缓存到页面。它设计理念是最小化配置(目标是零配置) 和 Webpack的深度整合。当 Webpack 的 publicPath 配置了,它能够自动为缓存生成 relativePaths,而不需要再指定其他配置。对静态网站来说,offline-plugin 是一个很好的 sw-precache 的替代品。如果你用的是 HtmlWebpackPlugin,offline-plugin 还能缓存 .html 页面。

module.exports = {
  plugins: [
    // ... other plugins
    new OfflinePlugin()
  ]
}

我在 渐进式 Web 应用的离线缓存 中讲了其他类型数据的离线存储策略。尤其是 React,如果你正关注添加数据仓库到缓存或正使用 Redux,你会对 坚持 Redux 和 Redux 复制本地搜索 感兴趣的(后者压缩后约 8 KB)。

迷你案例学习:为 ReactHN 添加离线缓存

ReactHN 一开始是没有离线缓存的单页应用。我们按步骤添加离线缓存:

第一步:用 sw-precache 为应用 “外壳” 离线缓存静态资源。通过调用 package.json 里 script 域的 sw-precache CLI 工具,每次构建完成时产生一个 Service Worker 用于预缓存外壳

"precache": "sw-precache — root=public — config=sw-precache-config.json"

这份预缓存配置文件通过上面的命令传递,可以控制引入的文件和 helper 脚本:

{
  "staticFileGlobs": [
    "app/css/**.css",
    "app/**.html",
    "app/js/**.js",
    "app/images/**.*"
  ],
  "verbose": true,
  "importScripts": [
    "sw-toolbox.js",
    "runtime-caching.js"
  ]
}

sw-precache 在输出结果中列出将离线缓存的静态资源总大小。这有利于明白多大的应用外壳和资源能够保证良好的交互体验。

注意:如果现在开始做离线缓存功能,我会只用 sw-precache-webpack-plugin 从标准 Webpack 配置中直接配置:

plugins: [
    new SWPrecacheWebpackPlugin(
      {
        cacheId: "react-hn",
        filename: "my-service-worker.js",
        staticFileGlobs: [
          "app/css/**.css",
          "app/**.html",
          "app/js/**.js",
          "app/images/**.*"
        ],
       verbose: true
      }
    ),

第二步:我们还想缓存运行时/动态请求。为了实现这一功能,我们需要引入 sw-toolbox 和上面的运行时缓存配置。应用使用了 Google Fonts 网络字体,所以我们添加一个简单的规则,缓存所有 google.com 的 fonts 子域下的请求。

global.toolbox.router.get('/(.+)', global.toolbox.fastest, {
   origin: /https?:\/\/fonts.+/
});

从 API 端点(例如一个 appspot.com 上的应用引擎)缓存数据请求,类似如下:

global.toolbox.router.get('/(.*)', global.toolbox.fastest, {
   origin: /\.(?:appspot)\.com$/
})

注意:sw-toolbox 支持许多有用的选项,包括能够设置缓存条目的最大失效时长(借助 maxAgeSeconds)。要了解更多支持细节,请阅读 API docs

第三步:仔细想一想对你的用户来说,什么是最有帮助的离线体验。每个应用都有所不同。

ReactHN 依赖服务器返回的实时新闻报道和评论数据。一番实验之后,我们发现 UX 和性能之间的一个平衡点是用 稍微 老旧的数据提供离线体验。

从其他已经发布的 PWA 上可以学到很多东西,鼓励大家尽可能地研究和分享学习成果。

离线 Google 分析

一旦在你的 PWA 使用 Service Worker 提升离线体验,你的关注点就会移向别处,比如,确保 Google 分析离线可用,如果你尝试离线 GA,请求会失败,你也不能得到有用的数据状态。

IndexedDB 中的离线 Google 分析事件队列

我们可以用 离线 Google 分析库 解决这一问题(sw-offline-google-analytics)来解决这一问题。当用户离线时,入队所有 GA 请求,并且一旦网络再次可用,就尝试重连。我们今年的 Google I/O web app 就成功使用了相似的技术,鼓励大家都去试一试。

普遍问题(和答案)

对我来说,Service Worker 最难搞的部分就是调试。但去年开始,Chrome DevTools 显著降低了调试难度。为了节约你的时间和减少稍后踩的大坑,我强烈推荐在 SW debugging codelab 上做开发。 :fearful:

记录你发现的技巧或者新知识也可以帮助别人。Rich Harris 就写了 Service Worker 早知道

根据其他内容集结了资料如下:

其他资源:

最后结语!

在这个系列的第四部分,我们会重点关注使用全局渲染来渐进增强 React.js 渐进式 Web 应用

如果你刚了解 React,Wes Bos 的 React 入门 很适合你。

感谢 Gray Norton, Sean Larkin, Sunil Pai, Max Stoiber, Simon Boudrias, Kyle Mathews, Arthur Stolyar 和 Owen Campbell-Moore 的评论。






原文发布时间为:2016年11月23日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
2月前
|
存储 JavaScript 前端开发
掌握现代Web开发的基石:深入理解React与Redux
【10月更文挑战第14天】掌握现代Web开发的基石:深入理解React与Redux
35 0
|
10天前
|
Kubernetes 安全 Devops
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
36 10
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
|
21天前
|
监控 前端开发 JavaScript
使用 MERN 堆栈构建可扩展 Web 应用程序的最佳实践
使用 MERN 堆栈构建可扩展 Web 应用程序的最佳实践
27 6
|
22天前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
34 2
|
27天前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
1月前
|
SQL 安全 前端开发
PHP与现代Web开发:构建高效的网络应用
【10月更文挑战第37天】在数字化时代,PHP作为一门强大的服务器端脚本语言,持续影响着Web开发的面貌。本文将深入探讨PHP在现代Web开发中的角色,包括其核心优势、面临的挑战以及如何利用PHP构建高效、安全的网络应用。通过具体代码示例和最佳实践的分享,旨在为开发者提供实用指南,帮助他们在不断变化的技术环境中保持竞争力。
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
113 1
|
1月前
|
数据可视化 数据库 开发者
使用Dash构建交互式Web应用程序
【10月更文挑战第16天】本文介绍了使用Python的Dash框架构建交互式Web应用程序的方法。Dash结合了Flask、React和Plotly等技术,让开发者能够快速创建功能丰富的数据可视化应用。文章从安装Dash开始,逐步介绍了创建简单应用程序、添加交互元素、部署应用程序以及集成更多功能的步骤,并提供了代码示例。通过本文,读者可以掌握使用Dash构建交互式Web应用程序的基本技巧和高级功能。
59 3
|
1月前
|
JavaScript 前端开发 持续交付
构建现代Web应用:Vue.js与Node.js的完美结合
【10月更文挑战第22天】随着互联网技术的快速发展,Web应用已经成为了人们日常生活和工作的重要组成部分。前端技术和后端技术的不断创新,为Web应用的构建提供了更多可能。在本篇文章中,我们将探讨Vue.js和Node.js这两大热门技术如何完美结合,构建现代Web应用。
37 4
|
1月前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
35 1
下一篇
DataWorks