从注册服务工作者向客户端页面发送消息

3

我正在尝试在更新发现函数(onupdatefound)被触发时向app.js发送一个变量(布尔类型),这样每当收到新的更新时,app.js就会知道并且我可以展示一个带有刷新按钮的弹窗。

大部分实现我已经完成了,但是我不清楚我的app.js将如何在"message" addEventListener上接收数据,因为我无法从中接收任何数据。

提前感谢你的帮助。

registerServiceWorker.js

    registration.onupdatefound = () => {
     console.log('sw onupdatefound');
     const installingWorker = registration.installing;
      installingWorker.onstatechange = () => {
      console.log('sw onstatechange',navigator.serviceWorker);
      if (installingWorker.state === 'installed') {
        console.log('sw onstatechange//',navigator.serviceWorker);

        if (navigator.serviceWorker.controller) {

          // available; please refresh." message in your web app.
          console.log('New content is available; please refresh...');

          navigator.serviceWorker.controller.postMessage({
            data: {
              toUpdate: true,
            },
          });

        } else {
          console.log('Content is cached for offline use.');
        }
      }
    };
  };

service-worker.js

self.addEventListener('fetch', (event) => {
 console.log('in fetch of s-w');
self.clients.matchAll().then(all => all.map(client => client.postMessage(event)));
});

self.addEventListener('message', (event) => {
 console.log('event msg from s-w', event.data);
if (event.data.toUpdate) {
 console.log('updating');
self.skipWaiting();
}
// Select who we want to respond to
self.clients.matchAll().then(all => all.map(client => client.postMessage(event.data)));
self.clients
 .matchAll({
   includeUncontrolled: true,
   type: 'window',
 })
.then((clients) => {
  clients.postMessage(event.data);
  if (clients && clients.length) {
    // Send a response - the clients
    // array is ordered by last focused
    clients[0].postMessage(event.data);
  }
 });
});

app.js

navigator.serviceWorker.onmessage = (event) => {
 console.log('event in app.js nav on msg', event);
 if (event.data.toUpdate) {
  alert('Please refresh your page to upadate service worker');
 }
};

window.addEventListener('message', (event) => { console.log('new event ====>', event); });
1个回答

4
在 CRA v3 中,可以将 onSuccessonUpdate 回调作为 options 传递给 serviceWorker.register。通常这些回调会更新应用程序存储,进而影响 UI 组件。对于最小的 CRA v3 演示:
// App.js
import React from 'react';
import './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {message: 'Hello World'};

    // setup callback to get
    // service worker installation status
    //
    this.props.listen(status => {
      this.setState({message: status});
    });
  }

  render() {
    return (
      <p className="App">{this.state.message}</p>
    );
  }
}

export default App;

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

const {listen, onUpdate, onSuccess} = setupStatusTransfer();

ReactDOM.render(
  <React.StrictMode>
    <App listen={ listen } />
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorker.register({
  onUpdate,
  onSuccess
});

function setupStatusTransfer() {
  let cb;
  let status;

  const send = s => {
    status = s;
    if(cb && status) cb(status);
  }

  const listen = callback => {
    cb = callback;
    send(status);
  };

  return {
    listen,
    onUpdate: makeSendStatus(send, 'update'),
    onSuccess: makeSendStatus(send, 'success')
  };
}

function makeSendStatus(send, status) {
  return _registration => {
    send(status);
  };
}

另请参见:在Create React App中更新服务工作者后通知用户


您是否尝试通知所有当前由服务工作者提供服务的打开标签页(窗口)?这可能会存在一些问题,因为您依赖于旧服务工作者正确处理被新注册提交给它的消息 - 最终您不知道当前活动服务工作者有多么过时

话虽如此,在React组件中处理postMessage应该使用componentDidMountcomponentWillUnmount生命周期方法(或useEffect钩子)。

以下简化的示例使用了一个带有workerize-loader的专用Web Worker,但原则仍然相同:

// App.js
import React from 'react';
import './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: '?'};

    const nf = new Intl.NumberFormat(navigator.language, {minimumIntegerDigits: 6});
    this.receive = event => {
      const { data: count } = event;
      if (typeof count === 'number') {
        this.setState({count: nf.format(count)});
      }
    };
  }

  componentDidMount() {
    this.props.worker.addEventListener('message', this.receive);
  }

  componentWillUnmount() {
    this.props.worker.removeEventListener('message', this.receive);
  }

  render() {
    return (
      <p className="App">{this.state.count}</p>
    );
  }
}

export default App;

// App.js with hooks
import React, { useState, useEffect } from 'react';
import './App.css';

function App({ worker }) {
  const [count, setCount] = useState('?');
  const subscribe = () => {
    return listenForUpdates(worker, setCount);
  }
  useEffect(subscribe, [worker]);

  return (
      <p className="App">{ count }</p>
  );
}

function listenForUpdates(worker, setCount) {
  const nf = new Intl.NumberFormat(
    navigator.language,
    {minimumIntegerDigits: 6}
  );

  const receive = ({ data })  => {
    if (typeof data === 'number') {
      setCount(nf.format(data));
    }
  };

  const cleanup = () => {
    worker.removeEventListener('message', receive)
  };

  worker.addEventListener('message', receive);

  return cleanup;
}

export default App;

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

// eslint-disable-next-line import/no-webpack-loader-syntax
import worker from 'workerize-loader!./worker';

let instance = worker();

instance.start(1000);

ReactDOM.render(
  <React.StrictMode>
    <App worker={ instance } />
  </React.StrictMode>,
  document.getElementById('root')
);

// worker.js for https://github.com/developit/workerize-loader
//
export function start(delay) {
  let count = 0;
  const sendAndUpdate = () => {
    postMessage(count);
    ++count;
  };

  setInterval(sendAndUpdate, delay);
}

addEventListenerremoveEventListener应该可以在从navigator.serviceWorker.controller获取的ServiceWorker接口上使用。

(你的app.js示例尝试使用navigator.serviceWorker.onmessage - 但navigator.serviceWorkerServiceWorkerContainerServiceWorker可以在navigator.serviceWorker.controller中找到 - 这就是onmessage属性所在的位置)。


更新:使用Create React App 4 ServiceWorker需要选择加入,即不再包括在默认的CRA模板中。

ServiceWorker 包含在 cra-template-pwa (创建 Progressive Web App) 中,它本身通过Workbox库使用 ServiceWorker。

npx create-react-app my-app --template cra-template-pwa

服务工作者的注册逻辑已经被分离到名为 serviceWorkerRegistration.js 的文件中,与 service-worker.js 分开。此外,服务工作者仅在提供生产版本时进行注册。 要构建,请执行以下操作:
yarn run build

然后像这样提供服务:

http-server -c-1 ./build

最小化的cra-template-pwa v4演示:

// file: registrationStatus.js
import { useState, useEffect } from 'react';

let current = 'Unchanged';
const subscriptions = new Set();

function subscribe(setStatus) {
  subscriptions.add(setStatus);

  return () => {
    subscriptions.delete(setStatus);
  };
}

function dispatch(status) {
  current = status;
  subscriptions.forEach((setStatus) => setStatus(status));
}

function useStatus() {
  const [status, setStatus] = useState(current);
  useEffect(() => subscribe(setStatus), []);

  return status;
}

// Updated but waiting for stale tabs to close
function onUpdate(_registration) {
  dispatch('Updated');
}

// Updated and running
function onSuccess(_registration) {
  dispatch('Successful');
}

export { useStatus, onUpdate, onSuccess };

// file: App.js

import { useStatus } from './registrationStatus';

function App() {
  const status = useStatus();

  return <p className="App">Registration status: {status}</p>;
}

export default App;

// file: index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
import { onUpdate, onSuccess } from './registrationStatus';

ReactDOM.render(
    <React.StrictMode>
    <App />
    </React.StrictMode>,
  document.getElementById('root')
);

serviceWorkerRegistration.register({
  onUpdate,
  onSuccess,
});

reportWebVitals();

@Akshay 添加了最小化的v4 cra-template-pwa演示。 - Peer Reynders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接