背景:
在使用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,也没有了意义。
一切又回到了起点。。。。
最终解决
由于刷新重新加载是肯定不会触发popstate的,所以可以replaceState后进行reload。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。