作者 | Oren Farhi
译者 | 王强
策划 | 李俊辰
ReadM™ 是一款免费且易用的阅读 Web 应用,它可以激励孩子们通过实时反馈来练习、学习、阅读和讲出英语,并提供了很好的体验。
本文最初发布于 Orizens 博客,经原作者 Oren Farhi 授权由 InfoQ 中文站翻译并分享。
在这篇文章中,我要分享的是为 ReadM™ 这款 PWA 应用的用户推送更新通知的方法。ReadM 使用了一个Service Worker 来添加脱机和缓存支持,这样当有可用更新时,就应该通知用户更新到最新的版本上。
在 CRA 中设置离线支持
在 cra 文档中,有一部分内容很好地解释说明了为什么你会考虑向你的应用添加离线支持——这里需要重点考虑一些因素,都在文档里解释得很清楚。
https://create-react-app.dev/docs/making-a-progressive-web-app/#why-opt-in
创建 PWA 并提供离线支持时,有一个要求就是要有一个支持它的服务 Worker。CRA 已经具备了很好的服务 Worker 能力。在主 index.js 中有非常清晰的免责声明,和用来提供离线 Worker 的可选加入代码。
它的说明很清楚:
// 如果你要让自己的应用离线工作并更快加载,你可以将下面的 unregister() 改为 register()。注意这会带来一些 pitfalls。
// 关于服务 worker 的更多信息: https://bit.ly/CRA-PWA
serviceWorker.unregister()
这一服务的实际代码及其作用都在 serviceWorker.js 中。要设置开箱即用的离线支持,serviceWorker 应该只需调用 register 方法:
serviceWorker.register();
CRA 中服务 Worker 的“速成班”
服务 Worker 是一个独立的异步层,与应用的 DOM 并行存在。在代码中,它默认是在 React 启动应用的主渲染后声明并运行的。
在 ReadM 中,主 index.tsx 包括一个包装 App 的存储提供器,用来提供 redux 存储层:
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.querySelector("#root")
)
serviceWorker.register(config)
传递给 register 函数的 config 对象,可能包括在 serviceWorker 生命周期中发生的一些有趣事件的回调。这些事件中有一个是 onUpdate(registration) 事件。一旦服务 Worker 已在浏览器中注册,就从服务 Worker 内部调用此函数。不过,这并不表示出现了任何变化。
registration 对象是一个服务 Worker registration——也就是服务 Worker 要激活的对象,它可以控制同一域下的多个页面,并充当浏览器中的一种“事件发射器”。
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
serviceWorker.js 中的代码侦听 onStateChange 事件并调用这个 onUpdate() 回调。
向 Redux Store 分发更新
开始切入正题:我们对 onUpdate() 方法感兴趣。下面的代码在配置中传递给服务 Worker。
registration.waiting 是从 registration 对象返回的服务 Worker(服务 Worker 本身)。
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/waiting
仅当它存在时,它才会侦听 ReadM 代码中的任何更改或更新,并在此之后简单地将一个 updateRead() 动作分发到 Redux 存储。store 对象在 index.ts 范围内可用(如上所述)。
serviceWorker.register({
onUpdate: registration => {
const waitingServiceWorker = registration.waiting
if (waitingServiceWorker) {
waitingServiceWorker.addEventListener("statechange", event => {
if (event.target.state === "activated") {
store.dispatch(updateReady())
}
})
}
},
})
在本例中,我决定采用简单的路由并用布尔值通知更新。同样,一旦 ReadM 注册为离线使用,就会调用 onSucces() 回调。在这里,此信息可用于指示应用处于离线模式。
// Pseudo code
serviceWorker.register({
onSuccess: registration => {
console.log("registered app for offline use. details:", registration)
},
})
关注点分离——React 和 UI
至此我们已经处理好了技术层,可以检查和验证是否有更新。现在是时候加上 UI 了。
在 ReadM 中我采用了一种简单的方法——App 组件使用 Redux 选择器 hook,从应用的主 reducer 获取 update 的值(我简化了此示例的代码——实际上我是在使用 store api hook):
const update = useSelector(selectUpdate)
简而言之,这个 jsx 包括 Toast 组件的一个条件渲染,用来通知用户更新消息和要执行的动作:
{
update && (
<Toast
text="Update is available. Please Close all tabs and reload."
header="Update"
actionLabel="Update"
onAction={() => window.location.reload()}
/>
)
}
“离线”注意事项
在采用这个解决方案之前,我花了一些时间仔细思考问题的背景——这对于应用的整体架构,以及作为维护者的我来说都很重要。
我想明确分离关注点——分开逻辑层和 UI 层。我在它们之间加了一个层(本例中是 Redux,其他选项也是可以的),这样就把它们区分开了。
ReadM 是免费的,请尝试一下。
作者介绍
Oren Farhi 是前端工程师和 JS 顾问。他的作品包括 ReadM™、Echoes Player、ngx-infinite-scroll 等。他撰写了《Angular 和 NgRx 的响应式编程》一书。这里是他的开源项目 列表。
延伸阅读
https://orizens.com/blog/show-a-pwa-update-with-redux-react-hooks-and-service%20workers/
End
持续关注【悲伤日记】,给您带来精选好文~