最近在业务中遇到了一个问题:
业务需求:A 页面为一个入口页面,点击 A 页面的按钮会通过window.localtion.href
跳转到 B 页面,完成业务后,B页面通过 histror.go(-1)
返回 A 页面。这时候 A 页面会通过接口请求刷新页面的状态。其中 A、B 都是 SPA 项目,但是在不同的域名上。如下图所示:
A -> B -> A
遇到的问题是返回到 A 页面,A 页面的 mounted
不会重新触发。和同事一起排查了一下,发现是 BFCache 机制导致的“问题”。
什么是 BFCache
“Back-Forward Cache” 又叫 “Page Cache” 或者 “Fast History Navigation.” 这种缓存和其他缓存的概念都不大一样1。
我理解的 BFCache 就是一种“快照”。浏览器解析 HTML、CSS、JS、各类媒体资源后,会把这一个结果暂存起来。用户返回的时候,浏览器可以不用重新解析 HTML、CSS、JS等等,直接将这个“快照”拿出来就可以了。这样在移动端能节省大量的资源。在交互体验上也有很大的优势。比如说在渲染懒加载的长列表的时候,返回到上一页,所有之前请求到的接口数据、DOM 节点都不会被销毁。用户点击进入长列表的某一项查看详情,点击返回的时候能够立即继续查看。这一点采用 SPA 方案的话,往往做不到2(如果你使用过 PC 端的 twitter 的话你应该知道我在说什么)。
下面是手头上的机器测试出来的结果。yes 表示 BFCache 生效。
/**
* 2020/11/13 测试
* +--------+---------+--------+
* | | default | chrome |
* +--------+---------+--------+
* | xiaomi | yes | no[1] |
* +--------+---------+--------+
* | iOS | yes | yes |
* +--------+---------+--------+
* | huawei | yes | no[1] |
* +--------+---------+--------+
*
* chrome 在 Android 系统上用的是 blink 内核,根据相关资料[2],与 WebKit 的 BFCache 实现不兼容。而 iOS 上用的是 WebKit 内核。所有有的支持 BFCache 有的不支持。
* [1] 需要打开 chrome://flags 开启 Back-forward cache (Chrome 86 Android)
* [2] https://developers.google.com/web/updates/2019/02/back-forward-cache
*/
Q:存在 BFCache 的情况下怎么实现之前描述的业务需求
第一种最直接的方法:B 页面通过 A 页面传的回调地址来跳转而不是 history.go(-1)。缺点是会影响 history 堆栈。要看具体的业务场景。
第二种是被 Cached 的页面会有一个特殊的属性,如果是的话,reload 当前页面。尝试下来这个是最靠谱的。
mounted() {
window.addEventListener('pageshow', persistHandler);
// ...
},
destroyed() {
window.removeEventListener('pageshow', persistHandler);
// ...
}
function persistHandler(event) {
if (event.persisted) {
window.location.reload();
}
}
查阅资料的过程中还发现有一些其他 hack,但是尝试了很多基本上都已经失效了34。还有文章讨论了 webview 的缓存。由于笔者在这方面知积累不够。有机会在做探讨。
其他资料
测试地址 BFCache 的地址
back-forward-cache-tester.glitch.me/
Refs:
- github.com/whatwg/html…
- developer.mozilla.org/en-US/docs/…
- developer.mozilla.org/en-US/docs/…
- webkit.org/blog/427/we…
- docs.google.com/document/d/…
- www.chromestatus.com/feature/581…
- github.com/LeuisKen/le…