做者: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。