场景

针对pushState操作完,浏览器后退,以及页面返回时历史页面的处理。

需要了解的知识点

处理方法

Page A 在跳转Page B前需要保存信息到url中,处理完后返回A,需要将其信息保留下来。

我们处理的方式是,在跳转B前会通过History.pushState(),将状态保留在hash值中,又不会触发hashChange导致页面更新。然后从B返回A或者浏览器后退返回A的时候,能够获取到hash中的状态,从而根据状态完成初始化。

问题与现象

页面A /#/a

通过pushState() 修改hash /#/a?v=1,然后浏览器回退按钮点击,页面并无变化。期待是会根据状态重新初始化。

通过pushState()修改hash /#/a?v=1, /#/a?v=2,然后点击浏览器回退按钮,页面会刷新了。

浏览器回退是触发了hashchange事件的,React-router中的hashHistory调用了History,通过阅读代码发现能够解释以上问题的原因。

1、history中使用自身的self history记录路由堆栈。但是pushState中并没有触发hashchange方法,也就没有记录该值。所以通过pushState改变的路径后,self history永远记录了/#/a,浏览器后退的时候触发了hashChange进入到逻辑层中

// https://github.com/ReactTraining/history/blob/v4.7.0/modules/createHashHistory.js
const handleHashChange = () => {
    const path = getHashPath()
    const encodedPath = encodePath(path)

    if (path !== encodedPath) {
      // Ensure we always have a properly-encoded hash.
      replaceHashPath(encodedPath)
    } else {
      const location = getDOMLocation()
      const prevLocation = history.location // 堆栈中的loaction,由于pushState改变,所以堆栈中的还是 /#/a

      if (!forceNextPop && locationsAreEqual(prevLocation, location))// 浏览器后退 /#/a 与/#/a对比,一样,所以返回
        return // A hashchange doesn't always == location change.

      if (ignorePath === createPath(location))
        return // Ignore this change; we already setState in push/replace.

      ignorePath = null

      handlePop(location)
    }
  }

2、pustate2次的时候反而正常的原因,a =>a1 => a2, a2返回a1的时候,是a1与a对比,不一致所以会调用handlePop 触发组件更新

如何处理这一问题

在pushState前使用History.replaceState()改变当前的值例如增加_t?new Date(),然后再pushState。这样无论是第一次还是第二次后退的时候永远对比的都是不一样的,从事解决后退无发触发更新的问题。

后记

通过研究history还有pushState,更加了解了前端路由的工作原理。以后遇到浏览器前进后退的问题,也能从容处理。

A => B 前通过pushState更改了hash /#/a?v=1,再跳转到B