浅谈PWA

5,977 阅读20分钟

1.PWA是什么

PWA全称Progressive Web App,即渐进式WEB应用。

一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能

  • 可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
  • 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
  • 实现了消息推送

上述这些特性将使得 Web 应用渐进式接近原生 App。

2.PWA特性

  • 渐进式:能确保每个用户都能打开网页
  • 响应式:PC,手机,平板,不管哪种格式,网页格式都能完美适配
  • 离线应用:支持用户在没网的条件下也能打开网页,这里就需要 Service Worker 的帮助
  • APP 化:能够像 APP 一样和用户进行交互
  • 常更新:一旦 Web 网页有什么改动,都能立即在用户端体现出来
  • 安全:PWA基于HTTPS协议
  • 可搜索:能够被引擎搜索到
  • 推送:做到在不打开网页的前提下,推送新的消息
  • 可安装:能够将 Web 想 APP 一样添加到桌面
  • 可跳转:只要通过一个连接就可以跳转到你的 Web 页面
PWA不是某种技术的描述,而是几种技术的合集,如图:

3.PWA怎样实现

      上面所说PWA可实现Web App 添加至主屏、可实现离线缓存,在断网或弱网状态下依然可以使用一些离线功能,不影响Web App体验以及可实现用户在不打开浏览器情况下实现类似于原生App离线消息推送功能。

      那么对于这些实现,PWA依赖于什么?

      主要依赖于manifest.json和service worker(在项目中可写为一个名为SW.js的文件并引入项目)

      manifest.json以一个json格式的文件被引入项目,它主要用来实现PWA页面的添加至主屏、定义App启动时的URL(因为PWA App本质上还是一个Web)等。

4.Service Worker

      Service Worker是Chrome团队提出的一个Web API,旨在给Web应用程序提供高级的可持续的后台处理能力。相比于曾经的Web Worker这种脱离主线程之外的缓存解决方法,Service Worker是持久的。因为web worker是临时的,每次做的事情的结果还不能被持久存下来,如果下次有同样的复杂操作,还得费时间的重新来一遍。


       Service Workers 就像介于服务器和网页之间的拦截器,能够拦截进出的HTTP 请求,从而完全控制你的网站。

最主要的特点

  • 在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。
  • 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)
  • 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求
  • 单独的作用域范围,单独的运行环境和执行线程
  • 不能操作页面 DOM。但可以通过事件机制来处理
  • 事件驱动型服务线程
Service Worker最新浏览器支持性(详情请见caniuse.com/#feat=servi…)


4.1 Service Worker的使用
  • 由于 Service Worker 要求 HTTPS 的环境,我们通常可以借助于 github page 进行学习调试。当然一般浏览器允许调试 Service Worker 的时候 host 为 localhost 或者 127.0.0.1 也是 ok 的。

  • Service Worker 的缓存机制是依赖 Cache API 实现的

  • 依赖 HTML5 fetch API

  • 依赖 Promise 实现

4.1.1 注册

     要安装 Service Worker, 我们需要通过在 js 主线程(常规的页面里的 js )注册 Service Worker 来启动安装,这个过程将会通知浏览器我们的 Service Worker 线程的 javaScript 文件在什么地方。

     

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/sw.js', {scope: '/'})
            .then(function (registration) {

                // 注册成功
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch(function (err) {

                // 注册失败:(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}

  • 这段代码首先是要判断 Service Worker API 的可用情况,支持的话咱们才继续谈实现,否则免谈了。

  • 如果支持的话,在页面 onload 的时候注册位于 /sw.js 的 Service Worker。

  • 每次页面加载成功后,就会调用 register() 方法,浏览器将会判断 Service Worker 线程是否已注册并做出相应的处理。

  • register 方法的 scope 参数是可选的,用于指定你想让 Service Worker 控制的内容的子目录。本 demo 中服务工作线程文件位于根网域, 这意味着服务工作线程的作用域将是整个来源。

    关于 register 方法的 scope 参数,需要说明一下:Service Worker 线程将接收 scope 指定网域目录上所有事项的 fetch 事件,如果我们的 Service Worker 的 javaScript 文件在 /a/b/sw.js, 不传 scope 值的情况下, scope 的值就是 /a/b

    scope 的值的意义在于,如果 scope 的值为 /a/b, 那么 Service Worker 线程只能捕获到 path 为 /a/b 开头的( /a/b/page1, /a/b/page2,...)页面的 fetch 事件。通过 scope 的意义我们也能看出 Service Worker 不是服务单个页面的,所以在 Service Worker 的 js 逻辑中全局变量需要慎用。

  • then() 函数链式调用我们的 promise,当 promise resolve 的时候,里面的代码就会执行。

  • 最后面我们链了一个 catch() 函数,当 promise rejected 才会执行。

       代码执行完成之后,我们这就注册了一个 Service Worker,它工作在 worker context,所以没有访问 DOM 的权限。在正常的页面之外运行 Service Worker 的代码来控制它们的加载。

4.1.2 安装

      在你的 Service Worker 注册成功之后呢,我们的浏览器中已经有了一个属于你自己 web App 的 worker context 啦, 在此时,浏览器就会马不停蹄的尝试为你的站点里面的页面安装并激活它,并且在这里可以把静态资源的缓存给办了。

      install 事件我们会绑定在 Service Worker 文件中,在 Service Worker 安装成功后,install 事件被触发。

      install 事件一般是被用来填充你的浏览器的离线缓存能力。为了达成这个目的,我们使用了 Service Worker 新的标志性的存储 cache API — 一个 Service Worker 上的全局对象,它使我们可以存储网络响应发来的资源,并且根据它们的请求来生成key。这个 API 和浏览器的标准的缓存工作原理很相似,但是是只对应你的站点的域的。它会一直持久存在,直到你告诉它不再存储,你拥有全部的控制权。

      localStorage 的用法和 Service Worker cache 的用法很相似,但是由于localStorage 是同步的用法,所以不允许在 Service Worker 中使用。 IndexedDB 也可以在 Service Worker 内做数据存储。

// 监听 service worker 的 install 事件
this.addEventListener('install', function (event) {
    // 如果监听到了 service worker 已经安装成功的话,就会调用 event.waitUntil 回调函数
    event.waitUntil(
        // 安装成功后操作 CacheStorage 缓存,使用之前需要先通过 caches.open() 打开对应缓存空间。
        caches.open('my-test-cache-v1').then(function (cache) {
            // 通过 cache 缓存对象的 addAll 方法添加 precache 缓存
            return cache.addAll([
                '/',
                '/index.html',
                '/main.css',
                '/main.js',
                '/image.jpg'
            ]);
        })
    );
});

  • 这里我们 新增了一个 install 事件监听器,接着在事件上接了一个 ExtendableEvent.waitUntil()方法——这会确保 Service Worker 不会在 waitUntil() 里面的代码执行完毕之前安装完成。

  • waitUntil() 内,我们使用了 caches.open() 方法来创建了一个叫做 v1 的新的缓存,将会是我们的站点资源缓存的第一个版本。它返回了一个创建缓存的 promise,当它 resolved 的时候,我们接着会调用在创建的缓存实例(Cache API)上的一个方法 addAll(),这个方法的参数是一个由一组相对于 origin 的 URL 组成的数组,这些 URL 就是你想缓存的资源的列表。

  • 如果 promise 被 rejected,安装就会失败,这个 worker 不会做任何事情。这也是可以的,因为你可以修复你的代码,在下次注册发生的时候,又可以进行尝试。

  • 当安装成功完成之后,Service Worker 就会激活。在第一次你的 Service Worker 注册/激活时,这并不会有什么不同。但是当 Service Worker 更新的时候 ,就不太一样了。

4.1.3 自定义请求响应

       走到这一步,其实现在你已经可以将你的站点资源缓存了,你需要告诉 Service Worker 让它用这些缓存内容来做点什么。有了 fetch 事件,这是很容易做到的。

      每次任何被 Service Worker 控制的资源被请求到时,都会触发 fetch 事件,这些资源包括了指定的 scope 内的 html 文档,和这些 html 文档内引用的其他任何资源(比如 index.html 发起了一个跨域的请求来嵌入一个图片,这个也会通过 Service Worker),这下 Service Worker 代理服务器的形象开始慢慢露出来了,而这个代理服务器的钩子就是凭借 scope 和 fetch 事件两大利器就能把站点的请求管理的井井有条。

      你可以给 Service Worker 添加一个 fetch 的事件监听器,接着调用 event 上的 respondWith() 方法来劫持我们的 HTTP 响应,然后你可以用自己的魔法来更新他们。

this.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request).then(function (response) {
            // 来来来,代理可以搞一些代理的事情

            // 如果 Service Worker 有自己的返回,就直接返回,减少一次 http 请求
            if (response) {
                return response;
            }

            // 如果 service worker 没有返回,那就得直接请求真实远程服务
            var request = event.request.clone(); // 把原始请求拷过来
            return fetch(request).then(function (httpRes) {

                // http请求的返回已被抓到,可以处置了。

                // 请求失败了,直接返回失败的结果就好了。。
                if (!httpRes || httpRes.status !== 200) {
                    return httpRes;
                }

                // 请求成功的话,将请求缓存起来。
                var responseClone = httpRes.clone();
                caches.open('my-test-cache-v1').then(function (cache) {
                    cache.put(event.request, responseClone);
                });

                return httpRes;
            });
        })
    );
});

       我们可以在 install 的时候进行静态资源缓存,也可以通过 fetch 事件处理回调来代理页面请求从而实现动态资源缓存。

两种方式可以比较一下:

  • on install 的优点是第二次访问即可离线,缺点是需要将需要缓存的 URL 在编译时插入到脚本中,增加代码量和降低可维护性;

  • on fetch 的优点是无需更改编译过程,也不会产生额外的流量,缺点是需要多一次访问才能离线可用。

        除了静态的页面和文件之外,如果对 Ajax 数据加以适当的缓存可以实现真正的离线可用, 要达到这一步可能需要对既有的 Web App 进行一些重构以分离数据和模板。

4.1.4 Service Worker版本更新

    /sw.js 控制着页面资源和请求的缓存,那么如果缓存策略需要更新呢?也就是如果 /sw.js 有更新怎么办?/sw.js 自身该如何更新?

      如果 /sw.js 内容有更新,当访问网站页面时浏览器获取了新的文件,逐字节比对 /sw.js 文件发现不同时它会认为有更新启动 更新算法,于是会安装新的文件并触发 install 事件。但是此时已经处于激活状态的旧的 Service Worker 还在运行,新的 Service Worker 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来重新打开的页面里生效。

4.1.5 自动更新所有页面

      如果希望在有了新版本时,所有的页面都得到及时自动更新怎么办呢?可以在 install 事件中执行 self.skipWaiting() 方法跳过 waiting 状态,然后会直接进入 activate 阶段。接着在 activate 事件发生时,通过执行 self.clients.claim() 方法,更新所有客户端上的 Service Worker。

// 安装阶段跳过等待,直接进入 active
self.addEventListener('install', function (event) {
    event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([

            // 更新客户端
            self.clients.claim(),

            // 清理旧版本
            caches.keys().then(function (cacheList) {
                return Promise.all(
                    cacheList.map(function (cacheName) {
                        if (cacheName !== 'my-test-cache-v1') {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});

      另外要注意一点,/sw.js 文件可能会因为浏览器缓存问题,当文件有了变化时,浏览器里还是旧的文件。这会导致更新得不到响应。如遇到该问题,可尝试这么做:在 Web Server 上添加对该文件的过滤规则,不缓存或设置较短的有效期。

4.1.6 手动更新Service Worker

在页面中,可手动借助 Registration.update() 更新。

var version = '1.0.1';

navigator.serviceWorker.register('/sw.js').then(function (reg) {
    if (localStorage.getItem('sw_version') !== version) {
        reg.update().then(function () {
            localStorage.setItem('sw_version', version)
        });
    }
});

      Service Worker 的特殊之处除了由浏览器触发更新之外,还应用了特殊的缓存策略: 如果该文件已 24 小时没有更新,当 Update 触发时会强制更新。这意味着最坏情况下 Service Worker 会每天更新一次。

4.2 Service Worker生命周期


当用户首次导航至 URL 时,服务器会返回响应的网页。

  • 第1步:当你调用 register() 函数时, Service Worker 开始下载。
  • 第2步:在注册过程中,浏览器会下载、解析并执行 Service Worker ()。如果在此步骤中出现任何错误,register() 返回的 promise 都会执行 reject 操作,并且 Service Worker 会被废弃。
  • 第3步:一旦 Service Worker 成功执行了,install 事件就会激活
  • 第4步:安装完成,Service Worker 便会激活,并控制在其范围内的一切。如果生命周期中的所有事件都成功了,Service Worker 便已准备就绪,随时可以使用了!

5.PWA实现举例

5.1manifest.json实现添加至主屏幕

index.html

<head>
  <title>Minimal PWA</title>
  <meta name="viewport" content="width=device-width, user-scalable=no" />
  <link rel="manifest" href="manifest.json" />
  <link rel="stylesheet" type="text/css" href="main.css">
  <link rel="icon" href="/e.png" type="image/png" />
</head>

manifest.json

{
  "name": "Minimal PWA", // 必填 显示的插件名称
  "short_name": "PWA Demo", // 可选  在APP launcher和新的tab页显示,如果没有设置,则使用name
  "description": "The app that helps you understand PWA", //用于描述应用
  "display": "standalone", // 定义开发人员对Web应用程序的首选显示模式。standalone模式会有单独的
  "start_url": "/", // 应用启动时的url
  "theme_color": "#313131", // 桌面图标的背景色
  "background_color": "#313131", // 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。
  "icons": [ // 桌面图标,是一个数组
    {
    "src": "icon/lowres.webp",
    "sizes": "48x48",  // 以空格分隔的图片尺寸
    "type": "image/webp"  // 帮助userAgent快速排除不支持的类型
  },
  {
    "src": "icon/lowres",
    "sizes": "48x48"
  },
  {
    "src": "icon/hd_hi.ico",
    "sizes": "72x72 96x96 128x128 256x256"
  },
  {
    "src": "icon/hd_hi.svg",
    "sizes": "72x72"
  }
  ]
}

5.2 Service Worker实现离线缓存

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello Caching World!</title>
  </head>
  <body>
    <!-- Image -->
    <img src="/images/hello.png" />                 
    <!-- JavaScript -->
    <script async src="/js/script.js"></script>     
    <script>
      // 注册 service worker
      if ('serviceWorker' in navigator) {           
        navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
          // 注册成功
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }).catch(function (err) {                   
          // 注册失败 :(
          console.log('ServiceWorker registration failed: ', err);
        });
      }
    </script>
  </body>
</html>

注:Service Worker 的注册路径决定了其 scope 默认作用页面的范围。
如果 service-worker.js 是在 /sw/ 页面路径下,这使得该 Service Worker 默认只会收到 页面/sw/ 路径下的 fetch 事件。
如果存放在网站的根路径下,则将会收到该网站的所有 fetch 事件。
如果希望改变它的作用域,可在第二个参数设置 scope 范围。示例中将其改为了根目录,即对整个站点生效。

service-worker.js

var cacheName = 'helloWorld';     // 缓存的名称  
// install 事件,它发生在浏览器安装并注册 Service Worker 时        
self.addEventListener('install', event => { 
/* event.waitUtil 用于在安装成功之前执行一些预装逻辑
 但是建议只做一些轻量级和非常重要资源的缓存,减少安装失败的概率
 安装成功后 ServiceWorker 状态会从 installing 变为 installed */
  event.waitUntil(
    caches.open(cacheName)                  
    .then(cache => cache.addAll([    // 如果所有的文件都成功缓存了,便会安装完成。如果任何文件下载失败了,那么安装过程也会随之失败。        
      '/js/script.js',
      '/images/hello.png'
    ]))
  );
});
  
/**
为 fetch 事件添加一个事件监听器。接下来,使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话,返回缓存的资源。
如果资源并不存在于缓存当中,通过网络来获取资源,并将获取到的资源添加到缓存中。
*/
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request)                  
    .then(function (response) {
      if (response) {                            
        return response;                         
      }
      var requestToCache = event.request.clone();  //          
      return fetch(requestToCache).then(                   
        function (response) {
          if (!response || response.status !== 200) {      
            return response;
          }
          var responseToCache = response.clone();          
          caches.open(cacheName)                           
            .then(function (cache) {
              cache.put(requestToCache, responseToCache);  
            });
          return response;             
    })
  );
});

注:为什么用request.clone()和response.clone()
需要这么做是因为request和response是一个流,它只能消耗一次。因为我们已经通过缓存消耗了一次,然后发起 HTTP 请求还要再消耗一次,所以我们需要在此时克隆请求

5.3 Service Worker实现消息推送


  • 步骤一、提示用户并获得他们的订阅详细信息
  • 步骤二、将这些详细信息保存在服务器上
  • 步骤三、在需要时发送任何消息

     不同浏览器需要用不同的推送消息服务器。以 Chrome 上使用 Google Cloud Messaging<GCM> 作为推送服务为例,第一步是注册 applicationServerKey(通过 GCM 注册获取),并在页面上进行订阅或发起订阅。每一个会话会有一个独立的端点(endpoint),订阅对象的属性(PushSubscription.endpoint) 即为端点值。将端点发送给服务器后,服务器用这一值来发送消息给会话的激活的 Service Worker (通过 GCM 与浏览器客户端沟通)。

步骤一+步骤二 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Progressive Times</title>
    <link rel="manifest" href="/manifest.json">                                      
  </head>
  <body>
    <script>
      var endpoint;
      var key;
      var authSecret;
      var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY';
      // 方法很复杂,但是可以不用具体看,只是用来转化vapidPublicKey用
      function urlBase64ToUint8Array(base64String) {                                  
        const padding = '='.repeat((4 - base64String.length % 4) % 4);
        const base64 = (base64String + padding)
          .replace(/\-/g, '+')
          .replace(/_/g, '/');
        const rawData = window.atob(base64);
        const outputArray = new Uint8Array(rawData.length);
        for (let i = 0; i < rawData.length; ++i) {
          outputArray[i] = rawData.charCodeAt(i);
        }
        return outputArray;
      }
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('sw.js').then(function (registration) {
          return registration.pushManager.getSubscription()                            
            .then(function (subscription) {
              if (subscription) {                                                      
                return;
              }
              return registration.pushManager.subscribe({                              
                  userVisibleOnly: true,
                  applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
                })
                .then(function (subscription) {
                  var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
                  key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
                  var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
                  authSecret = rawAuthSecret ?
                    btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
                  endpoint = subscription.endpoint;
                  return fetch('./register', {                                         
                    method: 'post',
                    headers: new Headers({
                      'content-type': 'application/json'
                    }),
                    body: JSON.stringify({
                      endpoint: subscription.endpoint,
                      key: key,
                      authSecret: authSecret,
                    }),
                  });
                });
            });
        }).catch(function (err) {
          // 注册失败 :(
          console.log('ServiceWorker registration failed: ', err);
        });
      }
    </script>
  </body>
</html>

步骤三 服务器发送消息给service worker

app.js

const webpush = require('web-push');                 
const express = require('express');
var bodyParser = require('body-parser');
const app = express();
webpush.setVapidDetails(                             
  'mailto:contact@deanhume.com',
  'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY',
  'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'
);
app.post('/register', function (req, res) {           
  var endpoint = req.body.endpoint;
  saveRegistrationDetails(endpoint, key, authSecret); 
  const pushSubscription = {                          
    endpoint: req.body.endpoint,
    keys: {
      auth: req.body.authSecret,
      p256dh: req.body.key
    }
  };
  var body = 'Thank you for registering';
  var iconUrl = 'https://example.com/images/homescreen.png';
  // 发送 Web 推送消息
  webpush.sendNotification(pushSubscription,          
      JSON.stringify({
        msg: body,
        url: 'http://localhost:3111/',
        icon: iconUrl
      }))
    .then(result => res.sendStatus(201))
    .catch(err => {
      console.log(err);
    });
});
app.listen(3111, function () {
  console.log('Web push app listening on port 3111!')
});

service worker监听push事件,将通知详情推送给用户

service-worker.js

self.addEventListener('push', function (event) {
 // 检查服务端是否发来了任何有效载荷数据
  var payload = event.data ? JSON.parse(event.data.text()) : 'no payload';
  var title = 'Progressive Times';
  event.waitUntil(
    // 使用提供的信息来显示 Web 推送通知
    self.registration.showNotification(title, {                           
      body: payload.msg,
      url: payload.url,
      icon: payload.icon
    })
  );
});

6.总结

PWA的优势

  • 可以将app的快捷方式放置到桌面上,全屏运行,与原生app无异
  • 能够在各种网络环境下使用,包括网络差和断网条件下,不会显示undefind
  • 推送消息的能力
  • 其本质是一个网页,没有原生app的各种启动条件,快速响应用户指令

PWA存在的问题

  • 支持率不高:现在ios手机端不支持pwa,IE也暂时不支持
  • Chrome在中国桌面版占有率还是不错的,安卓移动端上的占有率却很低
  • 各大厂商还未明确支持pwa
  • 依赖的GCM服务在国内无法使用
  • 微信小程序的竞争

尽管有上述的一些缺点,PWA技术仍然有很多可以使用的点。

  • service worker技术实现离线缓存,可以将一些不经常更改的静态文件放到缓存中,提升用户体验。
  • service worker实现消息推送,使用浏览器推送功能,吸引用户
  • 渐进式开发,尽管一些浏览器暂时不支持,可以利用上述技术给使用支持浏览器的用户带来更好的体验。

针对公司公测平台项目启发,个人探索思考对于PWA针对公司产品研发的可行性观点如下:

      PWA鉴于尚未成熟,但最大优势是提升Web App的体验,个人在浏览完美校园公测平台的测试项目中对PWA的实际工作应用有所思考——首先,对于个人近期参与过的《失物招领》轻应用来说:失物招领中信息流列表内的各个“丢失”“捡到”的帖子性质其实是一个长期存在的信息帖,因为有的失去物品或捡到物品可能找回难度很大,其次,对于失去物和捡到物的信息描述来说,每个帖子的内容相比于社交类信息帖来说,信息更改不会很频繁,涉及的交互主要可能是页面内的联系失主或捡到者功能,所以对于这种信息流更新不会太频繁但又适合非联网或弱网情况下随时随地能查看帖子信息与发布者联系(尤其是通过获取联系电话、QQ号码或微信号这种直接联系方式)的情况下,个人观点适合采用PWA技术,或者PWA与上上周志兵所介绍的Index DB相结合的方式来对这种应用进行改造,可以对后端服务器等成本有效减轻。其次,看到公测平台内有《课程表》应用和《校历》应用,同样可以采用PWA进行改造或开发,因为这两种应用的信息更新频率不会太快,如课程表和校历可能每学期会学校更新一次,且有的更新幅度不会太大,更新点不会太多,适合Service Worker进行缓存处理(此处不代表Service Worker就不能处理相对大一点的缓存)。

对于PWA的使用需要根据项目性质进行评估,希望有朝一日可以对一些内部项目实现PWA化,从前端角度减轻项目整体中的部分或整体成本,个人认为,前端开发人员的核心能力和竞争力非几个框架或工具的掌握,而是使用正确的技术手段减少项目的相关成本。

但个人能力尚浅薄,希望继续努力,先以掌握并精通相关必要技术为前提。