PWA漸進式Web應用

Progressive Web App,簡稱 PWA,是提升 Web App 的體驗的一種新方法,能給用戶原生應用的體驗。

目錄:

1、PWA 基本介紹

PWA 全稱 Progressive Web App,即漸進式 WEB 應用

2、PWA 核心技術揭祕

  1. Web app manifest
  2. Service worker
  3. Promise / async / await
  4. Fetch api
  5. Cache storage
  6. 常見的緩存策略
  7. notification
  8. test

3、PWA 項目實戰

內容:

1.PWA 基本介紹

  1. PWA:漸進式 Web 應用,MDN 地址:https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
  2. Progressive Web App,簡稱 PWA,是提升 Web App 的體驗的一種新方法,能給用戶原生應用的體驗。
  3. PWA 運用現代的 Web APP 以及傳統的漸進式增強策略來創建跨平臺 Web 應用程序。
  4. PWA 能做到原生應用的體驗不是特指某一項技術,而是經過應用一些新技術進行改造
  5. 只要你擁有一個 Web App,那麼 PWA 的旅程就開始了
  6. 現在 Vue 和 React 的腳手架中都已經集成了 PWA 功能

2.PWA 的優勢

  1. 漸進式
    適用於所有瀏覽器,因爲它是以漸進式增強作爲宗旨開發的。
  2. 流暢
    能夠藉助 Service Worker 在離線或者網絡較差的情況下進行正常訪問。
  3. 可安裝
    用戶可以添加常用的 webApp 到桌面,免去去應用商店下載的麻煩
  4. 原生體驗
    可以和 app 一樣,擁有首屏加載動畫,可以隱藏地址欄等沉浸式體驗。
  5. 黏性
    通過推送離線通知等,可以讓用戶迴流。

3.PWA 核心技術揭祕

1.Web App manifest:應用程序清單

  1. 基本介紹

    Web App manifest 可以讓網站安全安裝到設備的主屏幕,而不需要用戶
    通過應用商店下載。
    Web App manifest:在一個 JSON 文本文件中提供有關應用程序的信息
    (如名稱、作者、圖標和描述)
    傳統的 Web App 入口:1、網址 2、書籤、收藏夾 3、直接搜索

    Web App manifest:

    • 可以添加到桌面,有唯一的圖標和名單
    • 有啓動時界面,避免生硬的過渡
    • 隱藏瀏覽器相關的 UI,比如地址欄等
  2. 使用步驟

    • 在項目根目錄下創建一個 manifest.json 文件。
    • 在 index.html 中引入 manifest.json
    • 在 manifest.json 文件中提供常見的配置。
    • 需要在 https 協議或者http://localhost 下訪問項目
  3. 常見配置:

    • name:用於指定應用的名稱,用戶安裝橫幅提示的名稱,和啓動動畫裏的文字。
    • short_name:應用的端名稱,用於主屏幕顯示
    • start_url:指定用戶從設備啓動應用程序時加載的 URL,可以是絕對路徑和相對路徑
    • icons:用於指定可在各種環境中用做應用程序圖標的圖像對象數組,144*144
    • background_color:用戶指定啓動動畫的背景色
    • theme_color:用於指定應用程序的主題顏色
    • display:用於指定 app 的顯示模式
    • fullscreen:全屏模式,所有可用的顯示區域都被使用,並且不現實狀態欄
    • standalone 讓這個應用看起來像一個獨立的應用程序,包括具有不同的窗口,在應用程序啓動器中擁有自己的圖標等。
    • minimal-ui 該應用程序將看起來像一個獨立的應用程序,但會有瀏覽器地址欄。

2. Service Worker

  1. 基本介紹

    • 一個標準的 PWA 程序,必須包含 3 個部分
      manifest.json
      service worker
      https 服務器或者http://localhost

    • W3C 組織早在 2014 年 5 月就提出過 Service Worker 這樣一個 html5 API,主要用來做持久的離線緩存。

    • 前端有很多性能優化的手段:CDN、CSS Sprite、文件的合併壓縮、異步加載、資源緩存等等,這
      些手段都是用來做性能優化的,但是如果斷網了,會發生什麼?

    • service worker 允許 web 應用在網絡環境比較差或者是離線環境下依然可以使用

    • service worker 可以極大的提升 web app 的用戶體驗

    • service worker 是一個獨立的 worker 線程,獨立於當前網頁鏈接,是一種特殊的 web worker

    • Web Worker 是臨時的,每次做的事情的結果還不能被持久存下來,如果下次有同樣的複雜操作,還得
      費時間的重來一遍

    • 一旦被 install,就永遠存在,除非手動 unregister

    • 用到的時候可以直接喚醒,不用的時候自動休眠

    • 可編程攔截代理請求和返回,緩存文件,緩存的文件可以被網頁進程取到(包括網絡離線狀態)

    • 離線內容開發者可控

    • 必須在 Https 環境下才能工作

    • 異步實現,內部大都是通過 Promise 實現

  2. 使用步驟

    1. 在 window.onload 中註冊 service worker,防止與其他資源競爭
    2. 在 navigator 對象中內置了 serviceWorker 屬性
    3. serviceWorker 在老版本的瀏覽器中不支持,需要進行瀏覽器兼容 if(‘serviceWordker’ in navigator){}
    4. 註冊 service worker navigator.serviceWorker.register(‘./sw.js’),返回一個 promise 對象

3. Promise

  1. 基本使用

    • Promise 是異步編程的一種解決方案,比傳統的解決方案—回調函數和事件—更合理更強大

    • Promise 可以鏈式的進行異步請求,解決了回調地獄的問題

    • Promise 常用的靜態方法

        1. Promise.resolve()返回一個解析過帶着給定值的 Promise 對象,如果返回值
        2. 是一個 Promise 對象,則直接返回這個 Promise 對象
        3. Promise.reject()靜態函數 Promise.reject 返回一個被拒絕的 Promise 對象
        4. Promise.all()返回一個 Promise 實例,等所有 promise 對象都成功了,纔會成功
        5. Promise.race()競速,只要有一個 promise 對象成功或者失敗了,結果就是成功或者失敗
      

4. async/await

  1. ES2017 標準引入 async 函數,使得異步操作變得更加方便
  2. async 用於修飾一個函數,async function fn(){},await 函數會返回一個 promise 對象
  3. await 只能出現在 async 函數中,await 後面跟一個 promise 對象,用於獲取 promise 對象成功的結果,
    如果不是 promise 對象,直接返回值
  4. await 後面的 promise 如果沒有成功,那麼會拋出異常,需要使用 try.catch 語法

5. fetch api

  1. Fetch API 提供了一個 Javascript 接口,用於訪問和操縱 Http 管道的部分,例如請求和響應
  2. 在 service worker,如果想要發送請求,無法使用 XMLHttpRequest,必須使用 fetch api
  3. Fetch.api 是基於 promise 實現的
  4. request 是一個二進制數據流,需要調用 json()方法可以轉換 json
  5. config 常見參數
    • body:用於設置請求體
    • headers:用於設置請求頭
    • method:用於設置請求方式

6. cache storage

  1. cacheStorage 接口表示 Cache 對象的存儲,配合 service worker 來實現資源的緩存
  2. caches api 類似數據庫的操作
    • caches.open(cacheName).then(function(catch){}):用於打開緩存,返回一個匹配 cacheName 的 cache 對象的 promise,類似於鏈接數據庫
    • caches.keys()返回一個 promise 對象,包括所有的緩存 key
    • caches.delete(key)根據 key 刪除對應的緩存(數據庫)
  3. cache 對象常用方法(單挑數據的操作)
    • cache 接口爲緩存的 Request/response 提供存儲機制
    • cache.put(req,res)把請求當成可以,並且把對應的響應存儲起來
    • cache.add(url)根據 url 發起請求,並且把響應結果存儲起來
    • cache.addAll(urls)抓去一個 url 數組,並且把結果都存儲起來
    • cache.match(req)獲取 req 對應的 response

7. 常見的緩存策略

Cache only

Network only
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ThkrMxuj-1579502717128)(https://m.360buyimg.com/img/jfs/t1/94162/24/7057/446668/5df89f4eE4447cca5/dfbb6edcaa343ffd.pngg)]
Cache,falling back to Network

Network,falling back to Cache

Cache & Network race

8. notification api

  1. Notification API 的通知接口用於向用戶配置和顯示桌面通知
  2. Notification.permission 可以獲取當前用戶的授權情況
    • default:默認的,未授權
    • Denied:拒絕的,如果拒絕了,無法再次請求授權,也無法彈窗提醒
    • Granted:授權的,可以彈窗提醒
  3. 通過 Notification.requestPermission()可以請求用戶的授權
  4. 通過 new Notification(‘title’,{body:’’,icon:’’})可以顯示通知
  5. 在授權通過的情況下,可以在 service worker 中顯示通知 self.registration.showNotification(‘您好’,{body:’msg’})

3.PWA 項目實戰

代碼展示:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="manifest" href="./manifest.json" />
    <title>PWA</title>
    <script>
      window.onload = function() {
        //serviceWorker
        // if ("serviceWorker" in navigator) {
        //   navigator.serviceWorker
        //     .register("./sw.js")
        //     .then(registration => {
        //       console.log("registration", registration);
        //     })
        //     .catch(err => {
        //       console.log(err);
        //     });
        // }

        //添加通知功能
        if (Notification.permission === "default") {
          Notification.requestPermission().then(result => {
            console.log(result);
          });
        }

        if (!window.navigator.onLine) {
          new Notification("提示", { body: "當前沒有網絡" });
        }

        if (window.navigator.onLine) {
          new Notification("提示", { body: "當前有網絡" });
        }
      };
    </script>
  </head>
  <body>
    Hello PWA
  </body>
</html>

mainfest.json

{
    "name": "PWA例子PWA例子",
    "short_name": "PWA",
    "start_url": ".",
    "display": "minimal-ui",
    "background_color": "#f8f8f8",
    "theme_color": "#ff0000",
    "description": "PWA例子",
    "icons": [
        {
            "src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
            "sizes": "144x144",
            "type": "image/png"
        },
        {
            "src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
            "sizes": "168x168",
            "type": "image/png"
        },
        {
            "src": "https://storage.jd.com/tenant-image/1/bigLogo.png",
            "sizes": "192x192",
            "type": "image/png"
        }
    ]
}

sw.js

// //緩存內容
// self.addEventListener('install', event => {
//   console.log('install', event);
//   //跳過等待,直接進入activate
//   //self.skipWaiting()等待結果,才進入activate
//   event.waitUntil(self.skipWaiting());
// });

// //主要清除舊的緩存
// self.addEventListener('activate', event => {
//   //跳過等待,直接進入active
//   console.log('activate', event);
//   //表示service worker激活後,立即獲取控制權
//   event.waitUntil(self.clients.claim());
// });

// //fetch事件會在請求發送的時候觸發
// self.addEventListener('fetch', event => {
//   //跳過等待,直接進入active
//   self.skipWaiting('fetch', event);
// });

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

const CACHE_NAME = "cache";
//緩存內容
self.addEventListener("install", async event => {
  // //開啓一個cache
  // const cache = await caches.open(CACHE_NAME);
  // //cache對象就可以存儲的資源
  // //等待cache把所有資源存儲起來
  // cache.addAll(['./index.html', './manifest.json', '/']);
  // await self.skipWaiting();

  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(["/", "/manifest.json", "/index.html"]);
    })
  );
});

//主要清除舊的緩存
self.addEventListener("activate", async () => {
  const keys = await caches.keys();
  keys.forEach(key => {
    if (key !== CACHE_NAME) {
      caches.delete(key);
    }
  });
  await self.clients.claim();
});

//fetch事件會在請求發送的時候觸發
//判斷資源是否能夠請求成功,如果請求成功,就響應成功的結果,如果斷網,就讀取caches緩存
self.addEventListener("fetch", event => {
  const req = event.request;
  const url = new URL(req.url);
  // if(url.origin !== self.origin){
  //   return
  // }
  event.respondWith(cacheFirst(req));

  // if(req.url.includes('./api')){
  //   event.respondWith(networkFirst(req))
  // }else{
  //   event.respondWith(cacheFirst(req))
  // }
});

//緩存優先
async function cacheFirst(req) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(req);
  //如果從緩存中得到
  if (cached) {
    return cached;
  } else {
    const fresh = await fetch(req);
    return fresh;
  }
}

//網絡請求優先,如果我們獲取到了數據,應該往緩存中存一份
async function networkFirst(req) {
  const cache = await caches.open(CACHE_NAME);
  try {
    const fresh = await fetch(req);
    cache.put(req, fresh.clone());
    return fresh;
  } catch (e) {
    const cached = await cache.match(req);
    return cached;
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章