MDN 里暂时还查不到的 Navigation API

发表于 5月以前  | 总阅读数:254 次

路由守卫

相信大家对路由守卫都不陌生,其实就是在页面当前发生导航变化时,在导航变化的前中后时机去做一些其他具体的事情。

SPA & History API

而在前端常见的业务场景,单页应用即 SPA 中,路由守卫功能则显得至为重要。目前主流的在 SPA 中实现路由守卫功能的方法,则是借助 History API 来实现的。基本原理是借助 window.history.pushState 以及 window.history.replaceState 来随时改变页面地址导航,再借助 window.onpopstate 或者 window.onhashchange 来监听页面的导航地址变化。

无法感知的 pushState & replaceState

然而,History API 也不是完全的银弹,主要在于导航地址监听器,只能监听到页面的前进后退,无法监听到 pushState 跟 replaceState。但是,一般页面上都会存在一些交互,会需要随时调用 pushState 或者 replaceState 来改变页面导航,与此同时,也需要相应地触发页面内的相关部分渲染更新。

为了解决这个无法感知的问题,通常有以下两种解决方案。

解决方案一

先注册自定义的 listeners,再给 push 跟 replace 再包一层,封装出来另外独立的 push 跟 replace 方法,之后每次调用的都是封装之后的方法,该方法内部会先真正执行 pushState 以及 replaceState 方法,之后再通知前面注册的 listeners 去执行。

使用这种解决方案的典型例子是 react-router,具体流程如下图

但是这种方案其实也是有局限性的,因为他依赖于其他模块都向同一个地方注册 listeners 并要求其他模块都去使用它自定义的封装过后的 push 跟 replace,其并没有提供一种通用规范的中心化的解决方案。假如现在页面中引入了另一部分会更新页面地址导航的逻辑,但是其并不使用前者封装的 push 或者 replace 的话,那么还是没办法触发页面渲染更新。

不过,接下来介绍的方案二,却可以解决上述问题。

解决方案二

通过直接暴力重写 window.history.pushState 跟 window.history.replaceState 方法,提供通用中心化解决方案。类似如下

const rewrite = function(type) {
  const hapi = history[type];
  return function() {
    // 可以在此处自定义更多的其他逻辑
    // ...
    const res = hapi.apply(this, arguments);
    // ...
    // 自定义抛出一个 popstate 事件,让其他部分监听 popstate 事件的代码,也能感知到
    const eventArguments = createPopStateEvent(window.history.state, type);
    window.dispatchEvent(eventArguments);
    return res;
  }
};

history.pushState = rewrite("pushState");
history.replaceState = rewrite("replaceState");

使用这种解决方案的典型例子是 Garfish,其关键实现代码如下

但是这种解决方案,也是有副作用的,毕竟暴力重写了全局方法,同时还自定义抛出了一个 popstate 事件。

试想一下,假如当前页面除了有 Garfish 之外,还有另外一个模块,该模块自己内部定义了一个经过封装的 push 方法,其每次调用该 push 方法时,会先调用经过 rewrite 的 window.history.pushState 并触发一次 popstate 事件,之后又会再通知模块内部的 listener 执行,与此同时,该模块内部也监听到了 popstate 事件并再一次执行了一次 listener,这时候,我们就会发现重复执行了两次 listener,这便是一个典型的副作用。

这么看下来,不管是用方案一还是用方案二,其实都或多或少有一些问题,那么,有没有其他更好的更通用的中心化的解决方案呢?在 MDN 文档里查了一圈,都没有发现比较好的方案,直到在 Chrome 里发现了 window.navigation。

Navigation API 横空出世

我们先看下 Chrome 的开发者文档里关于 Navigation API 的简介,如下

可以看到对于 Navigation API 定位是现代前端原生路由。同时也重点声明了可以用 Navigation API 重新构建 SPA 的。

NavigateEvent

Navigation API 里比较核心重要的部分,就是 navigate event 了。使用示例如下:

navigation.addEventListener('navigate', navigateEvent => {
  switch (navigateEvent.destination.url) {
    case 'https://example.com/':
      navigateEvent.transitionWhile(loadIndexPage());
      break;
    case 'https://example.com/cats':
      navigateEvent.transitionWhile(loadCatsPage());
      break;
  }
});

为什么需要增加一个 NavigateEvent

我们过往在结合 History API 实现 SPA 的时候,为了能感知到 pushState 以及 replaceState,我们是需要通过做很多其他的工作才能做到的,但是,有了 navigate event 之后,我们就可以轻轻松松通过添加一个事件监听器,就能监听到绝大部分的地址导航变化。现在我们再一次执行 pushState 以及 replaceState 的时候,是可以被 navigate 事件的监听器监听并感知到的。总的来说,是一种原生的更加通用且中心化的方式。

Transition

Transition 顾名思义,就是可以在页面发生 navigate event 时,做一些自定义的过渡的操作。其中主要是使用 transitionWhile(),他接受一个 Promise 类型参数,使用方式是,在 navigate 事件监听器内执行,他的执行,代表着告诉浏览器目前正在准备新的状态新的页面,这是需要耗费一定时间的,至于具体耗费多长时间,取决于传入的 Promise 何时 resolved 或者 rejected。

navigation.addEventListener('navigate', navigateEvent => {
  if (isCatsUrl(navigateEvent.destination.url)) {
    const processNavigation = async () => {
      const request = await fetch('/cat-memes.json',);
      const json = await request.json();
      // TODO: do something with cat memes json
    };
    navigateEvent.transitionWhile(processNavigation());
  } else {
    // load some other page
  }
});

Transition Success and Failure

前面已经提到传入 transitionWhile() 的 Promise 参数,是有可能成功 resolved 也有可能失败 rejected 的,而这两种状态,分别对应着 Transition Success 以及 Transition Failure,继而也对应着 navigatesuccess 以及 navigateerror 两个事件。

当 Promise 达到 fulfills 时,或者是压根就没有调用 transitionWhile(),那么 Navigation API 将会触发一个 navigatesuccess 事件。

navigation.addEventListener('navigatesuccess', event => {
  loadingIndicator.hidden = true;
});

当 Promise rejects 时,Navigation API 则会触发一个 navigateerror 事件。

navigation.addEventListener('navigateerror', event => {
  loadingIndicator.hidden = true; // also hide indicator
  showMessage(`Failed to load page: ${event.message}`);
});

导航取消 Abort Signals

假如当前页面还正在导航跳转时,突然被强占了,比如用户这时突然又点击了另外一个链接进行访问或者代码里直接执行了另外一个导航,为了应对这种情况,我们在传送给 navigate 的事件监听器的 event 参数对象里,多增加了一个 property 即 signal,类型为 window.AbortSignal。可以结合 AbortSignal 及 fetch 来实现 Abortable fetch,方法是,将 AbortSignal 传给 fetch,如果当前导航跳转被抢占了,则可以立即取消掉相应的网络请求,这样既可以节省用户的带宽,又可以将 fetch 返回的 Promise 置为 rejected 的状态,以防止任何无效的代码更新页面导致出现无效非法的导航页面。

navigation.addEventListener('navigate', navigateEvent => {
  if (isCatsUrl(navigateEvent.destination.url)) {
    const processNavigation = async () => {
      const request = await fetch('/cat-memes.json', {
        signal: navigateEvent.signal,
      });
      const json = await request.json();
      // TODO: do something with cat memes json
    };
    navigateEvent.transitionWhile(processNavigation());
  } else {
    // load some other page
  }
});

Entries

Navigation API 也有 Entries 概念,代表的是导航页面入口。可以通过 navigation.currentEntry 获取到当前用户所在的导航页面入口,也可以通过 navigation.entries() 获取到用户导航访问过的所有入口的列表。其中,Entry 在 Web IDL 中的规范定义如下

interface NavigationHistoryEntry : EventTarget {
  readonly attribute USVString? url;
  readonly attribute DOMString key;
  readonly attribute DOMString id;
  readonly attribute long long index;
  readonly attribute boolean sameDocument;

  any getState();

  attribute EventHandler ondispose;
};
  • url:导航会话的 URL 地址
  • key:在导航会话历史栈中的唯一标识,id 与 key 的区别在于,key 标识是在栈中的唯一标识,id 是 NavigationHistoryEntry 实例的唯一标识。例如:调用 replace 或 reload 时并没有产生新的导航会话,但会生成新的 NavigationHistoryEntry,前后两个 NavigationHistoryEntry 实例的 key 相同,但 id 不同。
  • id:导航会话的唯一标识
  • index:指示该导航会话在历史栈的位置,默认从 0 开始
  • sameDocument:true 代表当前是处于激活状态,false 则表示未激活
  • getState:返回导航会话存储的状态,类似 history.state
  • ondispose:监听 dispose 事件,在该导航会话从历史栈中删除时触发

可以通过 getState() 来获取 Entries 的 State,例如 navigation.currentEntry.getState(),这里的 State 也可以通过 navigation.updateCurrentEntry({state: something}); 来更新。

导航操作

  • navigation.navigate(url: string, options:state: any, history: 'auto' | 'push' | 'replace')

打开目标地址页面,相等于 history.pushState 和 history.replaceState,但是支持跨域地址。

  • navigation.reload({ state: any })

刷新当前页面,相当于调用了 location.reload()

  • navigation.back()

在导航会话历史中向后移动一页,相当于 history.back()

  • navigation.forward()

在导航会话历史中向前移动一页,相当于 history.forward()

  • navigation.traverseTo(key: string)

在导航会话历史记录中加载特定页面,相当于 history.go(),但区别在于传参不同,navigation 给每个导航会话设置了一个唯一标识,traverseTo 接受的参数正是该唯一标识,即 NavigationHistoryEntry.key。

不足

新 API,兼容性不好

实际上,Navigation API 是从 Chrome 102 才开始支持的,查了下笔者的 Chrome 版本,也才刚到 103 版本。。。。。。

展望

本文所阐述的内容,核心并不是为了详尽详细地介绍 Navigation 各个 API 的使用细节,而是想表达一下目前用 History API 来实现 SPA 所涉及的问题,并延伸介绍一下实现 SPA 的更好解决方案。个人认为,Navigation API 将有可能是未来的趋势,或许在不久的将来,他将是实现 SPA 的主要方案,而 History API 则可能更多成为一种 fallback 方案。

参考文献

  1. https://developer.chrome.com/docs/web-platform/navigation-api
  2. https://wicg.github.io/navigation-api/

https://mp.weixin.qq.com/s/D4qm9SwS14mTUYS64skfGw

 相关推荐

集体大降薪?有员工吐槽:再降要去公园卖鱼

6月5日,一张券商降薪截图在社交媒体疯传。截图提到,当日上午,某中字头头部券商召开大会,除了MD外全员降薪,且降薪不只是降奖金,而是直接降底薪。按照职级不同,SA1降6K,SA3降8K,VP降8K—10K。据了解,降薪大概率整体属实,但具体幅度有所差异,且不同区域、不同业务条线目前掌握的降薪情况也不尽相同。

发布于:2天以前  |  1674次阅读  |  详细内容 »

或搭载骁龙 8 Gen2,李斌透露蔚来手机新进展

今日,蔚来 CEO 李斌在 2023 高通汽车技术与合作峰会上爆料,蔚来第二代技术平台的全系车型已标配第三代骁龙座舱平台。

发布于:13天以前  |  679次阅读  |  详细内容 »

Meta AI大模型能识别4000多种语言,称错误率仅有OpenAI产品的一半

Meta公司周一(5月22日)推出了一个开源AI语言模型——大规模多语言语音(Massively Multilingual Speech, MMS)模型,可以识别和产生1000多种语言的语音——比目前可用的模型增加了10倍。研究人员表示,他们的模型可以转换1000多种语言,但能识别4000多种语言。

发布于:15天以前  |  627次阅读  |  详细内容 »

“AI孙燕姿”火遍全网!孙燕姿发文回应:人类无法超越AI,你是可定制的

歌手孙燕姿在更新动态中回应了近日引发争议的“顶流AI歌手孙燕姿”,笑称粉丝已经接受她是“冷门”歌手,而AI成为了目前的顶流。

发布于:15天以前  |  596次阅读  |  详细内容 »

荣耀回应新设公司自研芯片传言:重点在终端侧核心软件、图形算法等研发

5月31日晚,荣耀方面对澎湃新闻记者表示,上海荣耀智能科技开发有限公司是荣耀位于上海的研究所,是荣耀在中国的5个研究中心之一,重点方向在终端侧核心软件、图形算法、通信、拍照等方面研究开发工作。荣耀强调,坚持以用户为中心,开放创新,与全球合作伙伴一起为用户提供最佳产品解决方案。

发布于:8天以前  |  320次阅读  |  详细内容 »

宣称“M1芯片速度最快”,苹果被罚20万元:M1 Pro和Max更快

据北京市市场监督管理局公示信息,5月24日,苹果电子产品商贸(北京)有限公司因发布虚假广告被北京市东城区市场监督管理局处以20万元的行政处罚。

发布于:8天以前  |  235次阅读  |  详细内容 »

因PC销售不景气,联想Q1裁员约5%

据外媒5月24日消息,全球最大的个人电脑制造商联想表示,在2023年1-3月期间,该公司裁员了约5%,这是由于PC市场不景气导致的。

发布于:13天以前  |  216次阅读  |  详细内容 »

博主发布“史上最清晰”小米汽车谍照:猎跑风格,体积“特别大”

日前,有网络博主号称拍摄到了小米首款汽车MS11的高清视频。从视频中可以看出,新车依旧包裹大面积的伪装,据该博主称,他之所以确定这是小米汽车,是因为靠近观察之后,发现它的三角形大灯轮廓和其最初手绘的小米汽车假想图几乎一模一样。

发布于:8天以前  |  215次阅读  |  详细内容 »

ChatGPT 之父警告:AI 可能灭绝人类,350 名 AI 权威签署联名公开信

超过 350 名从事人工智能工作的高管、研究人员和工程师签署了这份由非盈利组织人工智能安全中心发布的公开信,认为人工智能具备可能导致人类灭绝的风险,应当将其视为与流行病和核战争同等的社会风险。

发布于:9天以前  |  207次阅读  |  详细内容 »

错失英伟达后,木头姐预测:AI的下一波机会在软件

日前,以押注“颠覆性创新”著称的ARK Invest创始人Cathie Wood在接受媒体采访时表示,软件提供商将是人工智能狂潮的下一个受益板块。英伟达每卖出1美元的硬件,软件供应商SaaS供应商就会产生8美元的收入。

发布于:8天以前  |  204次阅读  |  详细内容 »

小米投资恩井汽车科技公司

小米产投管理合伙人孙昌旭对此表示,小米产投将充分运用产业资源,与恩井科技形成高度业务协同,助力公司实现跨越式发展。

发布于:11天以前  |  192次阅读  |  详细内容 »

阿里云首席安全科学家吴翰清离职,投身AI短视频创业

据报道,阿里巴巴研究员吴翰清已于近期离职,钉钉显示其离职时间是5月19日。在阿里内部,研究员的职级为P10。据消息人士透露,吴翰清离职后,选择AI短视频赛道创业,已经close一轮融资。对于上述消息,截至发稿,阿里尚未回应。

发布于:13天以前  |  190次阅读  |  详细内容 »

阿里巴巴否认裁员传言,今年预估新招15000人

阿里巴巴集团官微宣布,2023年六大业务集团总计需新招15000人,其中校招超过3000人。同时表示,“近日,关于淘宝天猫、阿里云、菜鸟、本地生活各个业务裁员谣言传得很厉害,但谣言就是谣言。我们的招聘正在紧锣密鼓的进行。”

发布于:13天以前  |  183次阅读  |  详细内容 »

李开复:AI2.0带来的市场机遇会比移动互联网大10倍

“现今每一个存在的应用都将被AI 2.0重构,我觉得整个AI大模型带来的机遇和技术浪潮,会比过去Windows和安卓大10倍。”李开复表示。

发布于:10天以前  |  177次阅读  |  详细内容 »

一文读懂苹果WWDC大会:头显Vision Pro正式发布,售价高达2.5万元

苹果发布Vision Pro头显,正式宣布开启空间计算时代;苹果还发布新款MacBook Air,新款Mac Studio,并展示了iOS17、iPadOS 17、macOS Sonoma和watchOS10等新系统;Vision Pro头显售价3499美元,将于2024年初正式在美国市场发售;华尔街并不看好Vision Pro,苹果股价周一创历史新高后由涨转跌。

发布于:3天以前  |  169次阅读  |  详细内容 »

车圈“地震”:长城汽车实名举报比亚迪,比亚迪强势回应

5月25日,长城汽车就比亚迪秦PLUS DM-i、宋PLUS DM-i采用常压油箱,涉嫌整车蒸发污染物排放不达标的问题进行举报。

发布于:13天以前  |  159次阅读  |  详细内容 »

贾跃亭开抖音号,IP 在美国,粉丝数量53.7万,关注前妻甘薇

近日,一个名为“贾跃亭”的抖音账号悄然出现,带有“FF创始人、合伙人、首席产品及用户生态官, LeEco 乐视创始人”等标签,IP 地址显示为美国。

发布于:9天以前  |  146次阅读  |  详细内容 »

英伟达史诗级暴涨后再放大招!推出E级AI超算,黄仁勋狂捧生成式AI

5月29日消息,继上周远超预期的财报业绩预测引得股价和市值史诗级暴涨后,今日,英伟达(NVIDIA)创始人兼CEO黄仁勋穿着标志性的皮衣,意气风发地出现在台北电脑展COMPUTEX 2023上,在主题演讲期间先是现场给自家显卡带货,然后一连公布涉及加速计算和人工智能(AI)的多项进展。

发布于:10天以前  |  146次阅读  |  详细内容 »

苹果官方:618将在天猫开启全球首次直播

近日,苹果位于天猫的Apple Store官方旗舰店挂出直播预告,表示将在5月31日晚19时开启官方直播,这也是苹果官方在电商平台的全球首次直播。

发布于:9天以前  |  145次阅读  |  详细内容 »

前京东集团副总裁梅涛成立生成式 AI 公司,投身多模态领域

前京东集团副总裁、京东探索研究院副院长梅涛自今年初离职后,确认在 AI 领域创业,成立生成式 AI 公司 HiDream.ai。

发布于:7天以前  |  144次阅读  |  详细内容 »
 相关文章
 目录