做者:Felix Gerschau翻譯:瘋狂的技術宅前端
原文:https://felixgerschau.com/how...react
未經容許嚴禁轉載程序員
Service Worker 很棒。它們使 Web 開發人員能夠實現之前原生應用專有的相似功能。這類功能是例如推送通知或後臺同步的離線功能。web
它們是漸進式 Web 應用的核心。可是在設置它們以後,彷佛很難完成涉及與 Web 應用交互的更復雜的事情。面試
在本文中,我將展現可用的選擇並最後進行比較。segmentfault
若是你查看 Service Workers 的 API,將會看到 Web Worker 和 Service Worker 有很是類似的接口。儘管有類似之處,但它們的意圖和功能卻大不相同:promise
它們能夠在多個標籤中使用,甚至在全部標籤關閉後仍然可使用。瀏覽器
它們僅限於一個標籤 。緩存
二者的共同點是它們無權訪問 DOM,沒法使用 postMessage API 進行通訊。你能夠將它們看做是具備擴展功能的 Web Worker。服務器
若是你想了解有關它們更多信息,請查看這個對話,儘管有些陳舊,但能夠個很好的概述這個話題。到 2020 年,Service Workers 的瀏覽器支持有了很大的改進。
對於任何來源,均可以有多個 Service Worker。如下內容返回當前控制頁面的活動 Service Worker:
navigator.serviceWorker.controller
若是要訪問其餘 Service Worker,則能夠經過 registration 接口訪問,該藉口使你能夠訪問如下位置的 Service Worker 狀態:
你能夠經過幾種不一樣的方式訪問 registration 接口。其中有一個 navigator.serviceWorker.ready
。它將返回一個能夠經過註冊解決的 promise:
navigator.serviceWorker.ready.then((registration) => { // At this point, a Service Worker is controlling the current page });
若是你想了解有關 Service Worker 生命週期的更多信息,請查看這篇文章:(https://bitsofco.de/the-servi...)。
正如我已經提到的,Service Worker 經過 postMessage
API 進行通訊。這不只容許他們與JavaScript主線程交換數據,並且還能夠將消息從一個Service Worker發送到另外一個Service Worker。
// app.js - Somewhere in your web app navigator.serviceWorker.controller.postMessage({ type: 'MESSAGE_IDENTIFIER', }); // service-worker.js // On the Service Worker side we have to listen to the message event self.addEventListener('message', (event) => { if (event.data && event.data.type === 'MESSAGE_IDENTIFIER') { // do something } });
這種單向通訊的用例是在等待服務的 Service Worker 中調用 skipWaiting
,而後將其傳遞爲活動狀態並控制頁面。這已在 Create-React-App 附帶的 Service Worker 中實現。我用此技術在漸進式 Web 應用中顯示更新通知,我在這篇文章(https://felixgerschau.com/cre...)中進行了解釋。
可是若是你想將消息發送回 Window
上下文甚至其餘 Service Worker,該怎麼辦?
有好幾種方法能夠將消息發送到 Service Worker 的客戶端:
我將爲你提供每一個方法的簡短示例,而後將它們進行比較,以查看哪一種方法最適合你的用例。
我沒有包含 FetchEvent.respondWith(),由於這僅適用於獲取事件,並且目前不受 Safari 瀏覽器支持。
顧名思義,MessageChannel API 設置了一個能夠發送消息的通道。
該實現能夠歸結爲3個步驟。
也能夠添加第四步,若是你想經過在 Service Worker 中調用 port.close()
來關閉鏈接的話。
在實踐中看起來像這樣:
// app.js - somewhere in our main app const messageChannel = new MessageChannel(); // First we initialize the channel by sending // the port to the Service Worker (this also // transfers the ownership of the port) navigator.serviceWorker.controller.postMessage({ type: 'INIT_PORT', }, [messageChannel.port2]); // Listen to the response messageChannel.port1.onmessage = (event) => { // Print the result console.log(event.data.payload); }; // Then we send our first message navigator.serviceWorker.controller.postMessage({ type: 'INCREASE_COUNT', }); // service-worker.js let getVersionPort; let count = 0; self.addEventListener("message", event => { if (event.data && event.data.type === 'INIT_PORT') { getVersionPort = event.ports[0]; } if (event.data && event.data.type === 'INCREASE_COUNT') { getVersionPort.postMessage({ payload: ++count }); } }
Broadcast API 與 MessageChannel 很是類似,可是它消除了將端口傳遞給 Service Worker 的需求。
在這個例子中,咱們看到只須要在兩側創建一個有相同名稱 count-channel
的通道。
咱們能夠將相同的代碼添加到其餘 WebWorker 或 Service Worker,後者也將接收全部這些消息。
在這裏,咱們從上方看到了相同的例子,但用了 Broadcast API:
// app.js // Set up channel const broadcast = new BroadcastChannel('count-channel'); // Listen to the response broadcast.onmessage = (event) => { console.log(event.data.payload); }; // Send first request broadcast.postMessage({ type: 'INCREASE_COUNT', }); // service-worker.js // Set up channel with same name as in app.js const broadcast = new BroadcastChannel('count-channel'); broadcast.onmessage = (event) => { if (event.data && event.data.type === 'INCREASE_COUNT') { broadcast.postMessage({ payload: ++count }); } };
Client API 也不須要傳遞對通道的引用。
在客戶端,咱們偵聽 Service Worker 的響應,在 Service Worker 中,用 self.clients.matchAll
函數提供給咱們的過濾器選項,選擇要發送響應的客戶端。
// app.js // Listen to the response navigator.serviceWorker.onmessage = (event) => { if (event.data && event.data.type === 'REPLY_COUNT_CLIENTS') { setCount(event.data.count); } }; // Send first request navigator.serviceWorker.controller.postMessage({ type: 'INCREASE_COUNT_CLIENTS', }); // service-worker.js // Listen to the request self.addEventListener('message', (event) => { if (event.data && event.data.type === 'INCREASE_COUNT') { // Select who we want to respond to self.clients.matchAll({ includeUncontrolled: true, type: 'window', }).then((clients) => { if (clients && clients.length) { // Send a response - the clients // array is ordered by last focused clients[0].postMessage({ type: 'REPLY_COUNT', count: ++count, }); } }); } });
postMessage
API提供了一個簡單靈活的接口,使咱們能夠將消息發送給 Service Worker。
Broadcast Channel API 是最容易使用的選項,但不幸的是,它的瀏覽器支持並非很好。
在剩下的兩個中,我更喜歡 Client API,由於這不須要將引用傳遞給 Service Worker。