深入理解React Router:从原理到实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.6 history库限制

2.6.1 history.block的使用限制

值得一提的是,history.block存在以下几个问题。

1.刷新页面后历史栈丢失

对于大多数情况,history.block都能很好地工作,但是如果刷新页面,则原内存将丢失。以browserHistory为例,allKeys会重新初始化为空,原历史栈记录将全部清空,而浏览器自身的历史栈记录在刷新后是不会丢失的,这种跟浏览器行为不一致之处造成了问题。例如对于browserHistory,有/a、/b、/c页面,从/a页面开始,按如下路径进行跳转:/a→/b→/c;如果在/c页面进行了history.block阻止,这时由于每次导航都在内存中维护了历史栈记录,知道整个历史栈的情况,所以能在调用history.go(-1)后退一次后,browserHistory再重新调用history.go(1)前进一次,即一次“后退”再“前进”,进而恢复到/c,达到了路径不变的效果。如果在/c页面调用history.block后再刷新,即在内存中的历史栈内容丢失之后,先调用history.block,再调用history.go(-1)或history.go(-2),或者单击浏览器地址栏侧的“后退”按钮。这时由于内存中历史栈被清空,没有可供参考的回退历史记录,因此在内部计算得到的跳转恢复变量delta值为0,这时虽然history.go(-1)或history.go(-2)已经生效,URL已经改变为/b或/a,但是URL地址并未如期恢复到/c,最终出现URL与页面内容不一致的情况。

2.移动端的限制

如果遇到移动端历史栈记录数量的上限为100或者在移动端window.confirm不生效等问题,则需要Native开发者配合支持。比如在iOS12移动端场景下,history.block使用的默认prompt为window.confirm,其不会如PC页面一样有弹窗提示,而是默认返回“false”,这将阻止浏览器跳转,而不是弹出提示框让用户进行确认。

3.hash路由问题

如果是hash路由,则有两个问题,一个问题是使用path作为历史栈记录,path路径与browserHistory的key作用一样,但没有使用key的原因是考虑到hash路由没有window.history.state可以使用。所以该问题是路径重复的问题,如栈中有两个/b路径,在寻找/b路径时,将不知道以哪一个为准。例如当前栈路径为/a→/b→/c→/b,如果此时在第一个/b页面调用了history.block,再单击浏览器的“后退”按钮,强制返回到了/a,则要恢复到/b,这时将不知道恢复到哪个/b。history将会错误地恢复到最后一个/b页面,与预期不符合。

另一个问题是可以在地址栏中手动更改hash进行一次入栈操作,而事实上由于手动更改hash没有调用push方法,内存中的历史栈不会感知到手动更改hash导致的入栈。若当前路径为/a→/b→/c,且在/c处调用了history.block,如果此时在地址栏中输入/b,则路径将为/a→/b→/→/b,这时应该调用history.go(-1)恢复到/c,以达到阻止跳转的目的。由于内存中的栈只有/a→/b→/c,history将错误地认为当前的/b(手动输入的/b)为/a之后的/b,则要从/b恢复到/c,应该是从第二个/b前进一步到/c,因而会错误地调用history.go(1)进行恢复,而不是调用history.go(-1)。结果是路径已经变为了/b(history判断需要前进一步,但是此时已经是最后的位置),但是页面还是/c的页面(页面被history.block所阻止)。

事实上,上述问题的本质在于history库无能力真正“阻止”URL的改变,目前history v4.x将历史栈保存在内存中,从而造成了种种问题。

注意,对于memoryHistory.block方法是可以放心使用的,因为路径都在内存中进行了维护,可认为memoryHistory没有浏览器这个“副作用”。由于其没有浏览器的“后退”按钮和对应的浏览器popstate等事件,所有的前进、后退动作都在内存中操作,所以如果进行了history.block操作,则无须进行如browserHistory、hashHistory中的地址恢复行为,仅需执行一次更新操作即可。