共计 2095 个字符,预计需要花费 6 分钟才能阅读完成。
背景:
在使用 Tarojs 开发的时候,发现在 ios 上跳转至其他页面,再返回回来会出现接口接连调用两次的情况。
排查过程:
1. 由于接口调用在组件的 didMount 里,所以看其他的组件也是 didMount 调用两次,这让我感觉很诧异,componentDidMount 这个生命周期应该不会调用两次的;
2. 我打开了 weinre 查看,发现了有两个一模一样的页面组件,如下图
因为是 router 层级上 double 了,所以所有组件都会实例化两次,此时我怀疑是 taro/router 的问题。
我又去看了一下果园是否也是有同样的情况,结果发现在 page componentDidMount 里调用的 tree/get 等首屏数据接口调用了两次。。。
去查看一下 @tarojs/router 的源码,发现
history 被 tarojs/router 改装了,history.listen 监听的回调回在 popstate 事件触发的时候触发。
现在的问题根源确定了是由于 popstate 事件的触发,导致了 tarojs/router 认为当前页面是进行了一次前端路由跳转,所以进行了两次页面级别的渲染,导致所有的组件实例的生命周期都走了两次。那么问题来了,明明我们根本没有利用 history 的特性,所有的跳转都是刷新式跳转,为什么这个 popstate 会触发呢。
3. 搞清楚 popstate 的触发机制
根据 MDN 的描述:
当活动历史记录条目更改时,将触发 popstate 事件。如果被激活的历史记录条目是通过对 history.pushState()的调用创建的,或者受到对 history.replaceState()的调用的影响,popstate 事件的 state 属性包含历史条目的状态对象的副本。
需要注意的是调用 history.pushState() 或 history.replaceState() 不会触发 popstate 事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在 Javascript 代码中调用 history.back())
理论上 popstate 是不会在我们的应用了触发的,我试验了一下这个东西究竟是何方神圣。
<button id=“a"> 点我跳转 </button>
var a = document.getElementById('a');
a.onclick = function() {location.href = '/aaa.html'}
window.addEventListener(‘popstate’, function handlePopState() {console.log('handlePopState')
}, false);
点击后返回,压根没有触发 popstate,不管在 ios 还是 android 上。
然后我将
location.href =‘/aaa.html’
替换成
history.replaceState(null, ”, ‘aaa.html’);
在 ios 上返回是无刷新式的返回,触发了 popstate 事件,仅仅 url 回退成 popstate.html。
在 android 上返回是刷新式返回,同样触发了 popstate 事件,然后会重新解析 / 加载 / 执行。
到这里已经确定了 location.href 的跳转后返回是不会触发 popstate 的,因为是刷新式的返回,那就是我们在业务里有动了 history 的 api,此时我怀疑是在业务底层代码使用了 history。
4. 查看业务底层跳转代码
看到 navigation.forward 在 H5 仅仅使用了 location.href 进行跳转,但是在此之前为了加 is_back 参数表示当前页面是通过回退到达的,使用了 history.replaceState。
我模拟一下这个操作
var a = document.getElementById('a');
a.onclick = function() {history.replaceState(null, '','popstate33.html?a=1');
location.href = '/aaa.html'
}
果不其然,在 ios 里在返回的时候会触发 popstate 事件,而 android 不会触发。
说明了 ios 上,依旧不能阻止 popstate 的触发。
5. 解决方案
由于在 ios 中返回页面会触发 popstate,在 tarojs/router 此时认为进行了一次前端路由
5.1 在进入页面后根据 is_back 进行一次 reload
5.2 在底层跳转代码包上进行再一次包装,跳转的时候不再使用 history.replaceState
然而。。。。。。。
tarojs/router 有这么一行 - -,也就是业务层不使用 history.replaceState,它自己本身也用了,我把这行注释掉,且业务上不使用 replaceState,ios 回退才不会触发 popstate。
所以。。。。
5.1 由于不能使用 replaceState 将 is_back 去掉,不然将无限循环刷新;
5.2 由于 tarojs/router 本身自己使用了 history.replaceState,也没有了意义。
一切又回到了起点。。。。