基于 Service Worker 进行多页面通信 | 项目复盘

我心飞翔 分类:javascript

一、项目简介

基于 Service Worker 实现在浏览器多页面(多标签页之间)进行消息同步。

二、项目背景

我们知道,在一个大型的后台管理项目中,我们往往需要管理复杂的角色权限逻辑、管理用户的个性化配置,对于数据展示的一致性有较高的要求。

例如,当用户同时打开多个浏览器页面进行浏览和操作时,由于数据不能在多页面同步,可能会导致用户的误操作或者误解。

为了减少用户的误操作和优化用户的使用体验,我们可以基于 Service Worker 进行多页面的消息同步,使得操作一个页面可以同时刷新多个页面的信息。

三、实践过程

基本原理

Service worker 也是 worker 的一种,也就是说,它运行在 worker 上下文中,不能直接访问 DOM。Service worker 的生命周期与页面无关,它可以自动控制指定 域/请求路径 下的所有客户端页面。它的生命周期主要分为以下几个过程:
sw-lifecycle.png

安装并调试

在 React 项目中,可以使用 create-react-app 的 pwa 模板

$ npx create-react-app my-app --template cra-template-pwa
$ npx create-react-app my-app-ts --template cra-template-pwa-typescript
 

安装成功,会在 src 目录下生成 service-worker.jsserviceWorkerRegistration.js 两个文件。

为了在本地进行测试,我们先改写下默认的 serviceWorkerRegistration.js文件
image.png
webpack.config.js 中使用 WorkboxWebpackPlugin 来打包 service-worker.js 文件,这里我们同样把生产环境的判断移除。
image.png
index.js 中安装注册
image.png

如果不想使用 cra 的模板来搭建,其实也很简单,不管使用 webpack 打包或者其他工具打包,我们只要保证最终的 service-worker.js 文件可以在项目的指定路径下直接访问即可。在 webpack 中我们可以借助 WorkboxWebpackPlugin 或者 CopyWebpackPlugin 来处理。

运行 yarn start --watch,我们看到 Service worker 已经成功注册并激活了。

image.png

在控制台的 Application 面板,我们可以看到当前域名下注册的文件,以及当前多少窗口/客户端处于Service worker 的控制下
image.png

通信

接下来,我们来看看在具体的代码中,我们如何实现多页面的消息通知。

改写 service-worker.js 文件

/* eslint-disable no-restricted-globals */
import { clientsClaim } from "workbox-core";
import { precacheAndRoute } from "workbox-precaching";

clientsClaim();
precacheAndRoute(self.__WB_MANIFEST);

// 监听来自网页客户端的消息
self.addEventListener("message", (event) => {
  if (event.data && event.data.type === "SKIP_WAITING") {
    self.skipWaiting();
  }
  var promise = self.clients.matchAll().then(function (clientList) {
    var senderID = event.source.id;
    // 消息不传递给发送者本身
    clientList.forEach(function (client) {
      if (client.id === senderID) {
        return;
      }
      client.postMessage({
        client: senderID,
        message: event.data,
      });
    });
  });
  if (event.waitUntil) {
    event.waitUntil(promise);
  }
});
 

在页面中监听

const useNotification = () => {
  const [data, setData] = useState({});
  useEffect(() => {
    if ("serviceWorker" in navigator) {
      let listener = (event) => {
        const clientId = event.data.client;
        console.log(`receive message from ${clientId}`);
        setData(event.data);
      };
      navigator.serviceWorker.addEventListener("message", listener);
      return () => {
        navigator.serviceWorker.removeEventListener("message", listener);
      };
    }
  }, []);
  const postMessage = useCallback((message) => {
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker.controller.postMessage(message);
    }
  }, []);
  return [data, postMessage];
};
 

实现效果

GIF.gif

四、总结思考

本文只是通过一个简单的 Demo 来梳理 Service worker 进行通信的基本步骤,更加复杂的应用只需要在这个 Demo 上进一步拓展即可。

基于 Service Worker 我们还可以实现很多功能,比如后台同步、资源缓存、请求拦截等。

参考
Service Worker Cookbook

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情

回复

我来回复
  • 暂无回复内容