在 CRA v3 中,可以将
onSuccess
和
onUpdate
回调作为
options
传递给
serviceWorker.register
。通常这些回调会更新应用程序存储,进而影响 UI 组件。对于最小的 CRA v3 演示:
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {message: 'Hello World'};
this.props.listen(status => {
this.setState({message: status});
});
}
render() {
return (
<p className="App">{this.state.message}</p>
);
}
}
export default App;
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
应该使用componentDidMount
和componentWillUnmount
生命周期方法(或useEffect
钩子)。
以下简化的示例使用了一个带有workerize-loader
的专用Web Worker,但原则仍然相同:
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;
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;
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import worker from 'workerize-loader!./worker';
let instance = worker();
instance.start(1000);
ReactDOM.render(
<React.StrictMode>
<App worker={ instance } />
</React.StrictMode>,
document.getElementById('root')
);
export function start(delay) {
let count = 0;
const sendAndUpdate = () => {
postMessage(count);
++count;
};
setInterval(sendAndUpdate, delay);
}
addEventListener
和removeEventListener
应该可以在从navigator.serviceWorker.controller
获取的ServiceWorker
接口上使用。
(你的app.js
示例尝试使用navigator.serviceWorker.onmessage
- 但navigator.serviceWorker
是ServiceWorkerContainer
,ServiceWorker
可以在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演示:
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;
}
function onUpdate(_registration) {
dispatch('Updated');
}
function onSuccess(_registration) {
dispatch('Successful');
}
export { useStatus, onUpdate, onSuccess };
import { useStatus } from './registrationStatus';
function App() {
const status = useStatus();
return <p className="App">Registration status: {status}</p>;
}
export default App;
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();