
2.5.5 history.block原理解析
history.block对应history v3.x中的listenBefore监听方法,history.block同时也服务于React Router的Prompt组件。在2.1节曾介绍过,history.block的目的是在一些表单还未输入完成的页面,对用户的意图跳转行为进行阻止或弹窗确认。
那么history.block是如何“拦截”浏览器导航的呢?以browserHistory为例,对于history.push和history.replace方法,其会在真正跳转前调用transitionManager.confirmTransitionTo 进行跳转确认,各类history都会在内部创建transitionManager:

transitionManager.confirmTransitionTo的主要作用是确认是否应该阻止跳转,其实现如下:


而history.block的真正作用是设置confirmTransitionTo中的prompt函数:

所以,在执行block后,prompt变量不为null。这样在使用history每次调用history.push、history.replace时,将检查prompt,因为prompt不为null将进行跳转拦截。但如果调用history.go或者单击浏览器的“前进”或“后退”按钮跳转该如何“拦截”?事实上,单击“前进”或“后退”按钮只能监听到popstate、hashchange事件,但是此刻浏览器地址已经改变了,所以为了“拦截”跳转,要做的是在地址发生变化后进行人工恢复。在此情况下,可调用revertPop进行地址恢复:

地址恢复的依据是2.5.2节介绍过的内存栈。根据内存栈(如browserHistory的allKeys)、已经发生跳转的地址,以及希望恢复的地址三部分信息,可以计算出恢复到目标地址所需的跳转恢复变量delta值。
在browser路径下,进行人工恢复会触发两次popstate事件,第一次在单击浏览器的“后退”或“前进”按钮时触发,第二次在调用history.go方法时触发。第一次触发popstate事件不会通知history.listen监听器,只有第二次触发popstate事件后在使用history.go恢复地址时,才会重新触发一次history.listen监听器,并触发一次渲染。看起来路径是不变的,原路径对应的组件会接受一次props更新。