浏览器historyAPI和Next路由掉坑记录

浏览器historyAPI和Next路由掉坑记录

浏览器操控历史记录真的好大一个坑啊,我的天,看上去好像99%的浏览器都对history兼容良好,实际上呢?啊,怎么某个操作突然就不好使了,为什么这里能跳到那里,为什么nextJS突然就报错了,是next的原因还是啥?
本文记录了开发一个“多步骤页面拆分成多个页面”的需求研发过程。

因为采用NEXT的SSR模式,每一次刷新页面都会发起一个新的http请求,导致之前的数据源清空,所以我们一开始采用pushState+replaceState的方式去做。其实所有的步骤还是放在同一页面,每一次的pushState让url变,同时更新步骤数据,让页面变化。

pushState 和 replaceState和监听onPopState

pushState:向历史记录中push一条新记录。
replaceState:更换当前的历史记录。
参数:pushState(stateObj,title,url)

  • stateObj是每个历史记录中保存的state数据,可以通过history.state获取和修改。建议虽然不用,也可以push一个标识值在里面,而不是传null,因为查到资料说监听popState事件时可能会查询state值是否变动。
  • title :一个暂时没有用,但是所有浏览器和文档都建议你保留的值,所以直接传''就好
  • url:主要使用,传入我们想要跳转的url。

 
window.addEventListener(‘popstate’, historyListener);
监听路由的前进后退触发事件,和pushState相配合,因为普通的页面跳转在很多浏览器内不会触发监听事件(尤其是移动端),只有pushState / replaceState之后的页面前进后退,才会触发这个监听事件。
它的作用在于:pushState变换url后,用户历史记录返回,如果没有监听到返回事件,页面就不会变动了。

pushState 和 replaceState是history中常用的两个API,都是用于url操控。15年出的标准,现在都2023年了基本都能支持上。
pushState的兼容性比replaceState更好,建议少用replaceState,因为移动端兼容性有坑。

标大字:第一个坑,啊啊啊敢相信在IOS的谷歌浏览器上replaceState没法修改当前页面的url??

 平时最相信的就是google浏览器了,而且看兼容性也是支持的,但是奇了怪了就是不起作用,准确点,页面确实被替换了,但是url不会变动。而且这个bug在IOS safari / 安卓各种奇葩浏览器上都没有问题,当时心态就是一个啊???
 查了好久资料,应该是google对历史记录有保护措施,对于这些修改历史记录的操作有限制造成的。行吧,毕竟IOS+google这个组合确实应该是隐私保密性最强的搭配了。听我说,IOS,谢谢你。
 但是,于是,这个replace的方案就被毙了。

于是我们修改了一下需求计划,将所有的replaceState事件改回了pushState,要替换路由的地方通过路由去跳转。

next的路由跳转大坑

先认识一下next的路由跳转吧。
import {router} from 'next/router'
常用API :push | replace | refresh | beforePopState | onlyAHashChange |changeState | scrollToHash 等等。
大部分顾名思义知道是做什么用的,但是next和其他框架不同的地方在于它的路由跳转是有CSR、SSR区别的。

根据NEXT的文档:https://nextjs.org/docs/app/api-reference/functions/use-router

  • router.push(href: string): Perform a client-side navigation to the provided route. Adds a new entry into the browser’s history stack.
  • router.replace(href: string): Perform a client-side navigation to the provided route without adding a new entry into the browser’s history stack.
  • router.refresh(): Refresh the current route. Making a new request to the server, re-fetching data requests, and re-rendering Server Components. The client will merge the updated React Server Component payload without losing unaffected client-side React (e.g. useState) or browser state (e.g. scroll position).

push / replace是一个客户端使用的路由跳转方式,NEXT文档中介绍它们类似于window.history.pushState和replaceState,但是据我实测还是很大区别的。

看下方的原文中标粗的地方,其中说明了路由的跳转使用方式,一开始我没看懂,盲目使用,结果跳不过去,页面直接报错,因为我的项目是SSR项目,使用push / replace拉不到服务端请求数据。
经我测试,一般来说push的用法:router.push(href: string)只能用于CSR,但是用router.push(urlObject:object)可以做到SSR的路由跳转,就跟window.location.href差不多。

再加粗一下坑:NEXT router跳转的bug本地测不出来,啊啊啊,本地怎么跳都是好的,本地在移动端上也是好的,结果到构建完的测试环境就不好了啊啊啊,一跳就报错。所以所有测试都要push到测试环境才能发现。这几天commit提交量直接创新高,什么KPI刷新机器(不是

API具体使用介绍
  1. push(url: Url, as?: Url, options?: TransitionOptions): Promise;
  2. replace(url: Url, as?: Url, options?: TransitionOptions): Promise;
    Url可以传入一个对象,而不是简单的字符串,UrlObject的参数常用的有:hash、pathname、search、query。
URL object

也就是说,如果我们想要实现/test/[test_query] ?test=111 这样的的动态路由使用push跳转,我们最好不要用string类型的url,而是用Urlobject中的query参数去跳转,这样可以达到pushState的效果。页面平滑转换。
动态路由的参数都是用query,比如我们要去/product/AAA?test=111,这样的路由,写法应该如下:router.push({ pathname: '/product/[ID]', query: { ID: 'AAA', test: 111} })
CSR使用 useRouter 获取动态路由参数,SSR可以在getInitialProps 通过 ctx.query 获取参数。
动态路由参考官方文档(英)
动态路由参考官方文档(中)
stackOverflow-How can I get (query string) parameters from the URL in Next.js?

asPath URL object

asPath是next提出的一个“展示路由”,假如你的url是/AAA,但是你不想被其他人知道路由是AAA,可以使用asPath修饰成BBB,只需要传入第二个参数即可。

监听事件
  1. beforePopState:和window.addEventListener(‘popstate’)效果类似,方便操作,但是经我测试有发现返回首个页面时这个事件触发不到,不知道是不是我的操作问题还是怎么的。
  2. event事件监听,总共提供了以下事件,不过我没有试过,可以试试看: “routeChangeStart”, “beforeHistoryChange”, “routeChangeComplete”, “routeChangeError”, “hashChangeStart”, “hashChangeComplete”
options

有以下4个参数 shallow、locale、scroll、unstable_skipClientCache
具体还是参考他们的官方文档使用,因为我用的是SSR,上面的参数很多用不到,scroll用的比较多,就是可以跳转自动上滑到顶部,unstable_skipClientCache可以跳转时强制刷新,shallow可以用作无刷新的浅路由。

Next路由的掉坑记录:无法后退

由上所述,后续我们弃用了replaceState,全部用pushState来开发。却突然发现一个大问题:我们的历史记录后退后退,退回到第一个使用pushState 的页面时,页面突然直接崩溃了。
而且这个bug本地是好的,构建完才有问题!
当时我的心态也崩了,整啥啊这是。

一开始的时候,我自己以为这是本地运行时的报错“back to same url”具体错误名我忘了,如果有遇到类似的报错提示的同学应该能理解到,就是next报错返回到了和之前相同的地址,导致报错,查了下是因为路由参数中的asPath相同,然后我试着用router.getRouterInfo()打印了一下当前的路由aspath,发现路由停止在pushState的那个页面,一直没有变过,于是在跳转路由时,同步修改了router.state中的asPath参数。

但是等我构建完才发现问题没有丝毫解决,此时的我很淡定,以为是router路由跳转和window原生跳转的问题,于是花了一点时间将所有的跳转方法改为了router提供的路由pushState方法。使用push达不到效果,只有改用changeState('pushState')才行。

然后信心满满地push上去之后,错误仍然存在!

啊?我心态又崩了,为啥呢,连原生的路由方法都解决不了这个问题,我还能咋办。

于是连夜上github上查isuee,关键词back button。一番搜索之后终于发现next的路由跳转在历史记录返回的时候就是这么坑。。。
issue链接:https://github.com/vercel/next.js/issues/33233
这个Fixed的解决方法中提出了可以ReplaceState替换当前路由来避免这个路由返回的问题。。
是的,然后我就在每个页面的一进来componentDidMount时先做了一个replaceState的操作,这样终于把这个问题给fix了。
就这样。俺的掉坑之旅完美解决。

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值