日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
MDN里暫時(shí)還查不到的NavigationAPI

路由守衛(wèi)

相信大家對(duì)路由守衛(wèi)都不陌生,其實(shí)就是在頁(yè)面當(dāng)前發(fā)生導(dǎo)航變化時(shí),在導(dǎo)航變化的前中后時(shí)機(jī)去做一些其他具體的事情。

10年積累的做網(wǎng)站、成都網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有阜新免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

SPA & History API

而在前端常見(jiàn)的業(yè)務(wù)場(chǎng)景,單頁(yè)應(yīng)用即 SPA 中,路由守衛(wèi)功能則顯得至為重要。目前主流的在 SPA 中實(shí)現(xiàn)路由守衛(wèi)功能的方法,則是借助 History API 來(lái)實(shí)現(xiàn)的?;驹硎墙柚?window.history.pushState 以及 window.history.replaceState 來(lái)隨時(shí)改變頁(yè)面地址導(dǎo)航,再借助 window.onpopstate 或者 window.onhashchange 來(lái)監(jiān)聽(tīng)頁(yè)面的導(dǎo)航地址變化。

無(wú)法感知的 pushState & replaceState

然而,History API 也不是完全的銀彈,主要在于導(dǎo)航地址監(jiān)聽(tīng)器,只能監(jiān)聽(tīng)到頁(yè)面的前進(jìn)后退,無(wú)法監(jiān)聽(tīng)到 pushState 跟 replaceState。但是,一般頁(yè)面上都會(huì)存在一些交互,會(huì)需要隨時(shí)調(diào)用 pushState 或者 replaceState 來(lái)改變頁(yè)面導(dǎo)航,與此同時(shí),也需要相應(yīng)地觸發(fā)頁(yè)面內(nèi)的相關(guān)部分渲染更新。

為了解決這個(gè)無(wú)法感知的問(wèn)題,通常有以下兩種解決方案。

?解決方案一?

先注冊(cè)自定義的 listeners,再給 push 跟 replace 再包一層,封裝出來(lái)另外獨(dú)立的 push 跟 replace 方法,之后每次調(diào)用的都是封裝之后的方法,該方法內(nèi)部會(huì)先真正執(zhí)行 pushState 以及 replaceState 方法,之后再通知前面注冊(cè)的 listeners 去執(zhí)行。

使用這種解決方案的典型例子是 react-router,具體流程如下圖

但是這種方案其實(shí)也是有局限性的,因?yàn)樗蕾囉谄渌K都向同一個(gè)地方注冊(cè) listeners 并要求其他模塊都去使用它自定義的封裝過(guò)后的 push 跟 replace,其并沒(méi)有提供一種通用規(guī)范的中心化的解決方案。假如現(xiàn)在頁(yè)面中引入了另一部分會(huì)更新頁(yè)面地址導(dǎo)航的邏輯,但是其并不使用前者封裝的 push 或者 replace 的話,那么還是沒(méi)辦法觸發(fā)頁(yè)面渲染更新。

不過(guò),接下來(lái)介紹的方案二,卻可以解決上述問(wèn)題。

?解決方案二?

通過(guò)直接暴力重寫 window.history.pushState 跟 window.history.replaceState 方法,提供通用中心化解決方案。類似如下

const rewrite = function(type) {
const hapi = history[type];
return function() {
// 可以在此處自定義更多的其他邏輯
// ...
const res = hapi.apply(this, arguments);
// ...
// 自定義拋出一個(gè) popstate 事件,讓其他部分監(jiān)聽(tīng) popstate 事件的代碼,也能感知到
const eventArguments = createPopStateEvent(window.history.state, type);
window.dispatchEvent(eventArguments);
return res;
}
};

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

使用這種解決方案的典型例子是 Garfish,其關(guān)鍵實(shí)現(xiàn)代碼如下

但是這種解決方案,也是有副作用的,畢竟暴力重寫了全局方法,同時(shí)還自定義拋出了一個(gè) popstate 事件。

試想一下,假如當(dāng)前頁(yè)面除了有 Garfish 之外,還有另外一個(gè)模塊,該模塊自己內(nèi)部定義了一個(gè)經(jīng)過(guò)封裝的 push 方法,其每次調(diào)用該 push 方法時(shí),會(huì)先調(diào)用經(jīng)過(guò) rewrite 的 window.history.pushState 并觸發(fā)一次 popstate 事件,之后又會(huì)再通知模塊內(nèi)部的 listener 執(zhí)行,與此同時(shí),該模塊內(nèi)部也監(jiān)聽(tīng)到了 popstate 事件并再一次執(zhí)行了一次 listener,這時(shí)候,我們就會(huì)發(fā)現(xiàn)重復(fù)執(zhí)行了兩次 listener,這便是一個(gè)典型的副作用。

這么看下來(lái),不管是用方案一還是用方案二,其實(shí)都或多或少有一些問(wèn)題,那么,有沒(méi)有其他更好的更通用的中心化的解決方案呢?在 MDN 文檔里查了一圈,都沒(méi)有發(fā)現(xiàn)比較好的方案,直到在 Chrome 里發(fā)現(xiàn)了 window.navigation。

Navigation API 橫空出世

我們先看下 Chrome 的開(kāi)發(fā)者文檔里關(guān)于 Navigation API 的簡(jiǎn)介,如下

可以看到對(duì)于 Navigation API 定位是現(xiàn)代前端原生路由。同時(shí)也重點(diǎn)聲明了可以用 Navigation API 重新構(gòu)建 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;
}
});

為什么需要增加一個(gè) NavigateEvent

我們過(guò)往在結(jié)合 History API 實(shí)現(xiàn) SPA 的時(shí)候,為了能感知到 pushState 以及 replaceState,我們是需要通過(guò)做很多其他的工作才能做到的,但是,有了 navigate event 之后,我們就可以輕輕松松通過(guò)添加一個(gè)事件監(jiān)聽(tīng)器,就能監(jiān)聽(tīng)到絕大部分的地址導(dǎo)航變化?,F(xiàn)在我們?cè)僖淮螆?zhí)行 pushState 以及 replaceState 的時(shí)候,是可以被 navigate 事件的監(jiān)聽(tīng)器監(jiān)聽(tīng)并感知到的??偟膩?lái)說(shuō),是一種原生的更加通用且中心化的方式。

?Transition?

Transition 顧名思義,就是可以在頁(yè)面發(fā)生 navigate event 時(shí),做一些自定義的過(guò)渡的操作。其中主要是使用 transitionWhile(),他接受一個(gè) Promise 類型參數(shù),使用方式是,在 navigate 事件監(jiān)聽(tīng)器內(nèi)執(zhí)行,他的執(zhí)行,代表著告訴瀏覽器目前正在準(zhǔn)備新的狀態(tài)新的頁(yè)面,這是需要耗費(fèi)一定時(shí)間的,至于具體耗費(fèi)多長(zhǎng)時(shí)間,取決于傳入的 Promise 何時(shí) 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

前面已經(jīng)提到傳入 transitionWhile() 的 Promise 參數(shù),是有可能成功 resolved 也有可能失敗 rejected 的,而這兩種狀態(tài),分別對(duì)應(yīng)著 Transition Success 以及 Transition Failure,繼而也對(duì)應(yīng)著 navigatesuccess 以及 navigateerror 兩個(gè)事件。

當(dāng) Promise 達(dá)到 fulfills 時(shí),或者是壓根就沒(méi)有調(diào)用 transitionWhile(),那么 Navigation API 將會(huì)觸發(fā)一個(gè) navigatesuccess 事件。

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

當(dāng) Promise rejects 時(shí),Navigation API 則會(huì)觸發(fā)一個(gè) navigateerror 事件。

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

導(dǎo)航取消 Abort Signals

假如當(dāng)前頁(yè)面還正在導(dǎo)航跳轉(zhuǎn)時(shí),突然被強(qiáng)占了,比如用戶這時(shí)突然又點(diǎn)擊了另外一個(gè)鏈接進(jìn)行訪問(wèn)或者代碼里直接執(zhí)行了另外一個(gè)導(dǎo)航,為了應(yīng)對(duì)這種情況,我們?cè)趥魉徒o navigate 的事件監(jiān)聽(tīng)器的 event 參數(shù)對(duì)象里,多增加了一個(gè) property 即 signal,類型為 window.AbortSignal??梢越Y(jié)合 AbortSignal 及 fetch 來(lái)實(shí)現(xiàn) Abortable fetch,方法是,將 AbortSignal 傳給 fetch,如果當(dāng)前導(dǎo)航跳轉(zhuǎn)被搶占了,則可以立即取消掉相應(yīng)的網(wǎng)絡(luò)請(qǐng)求,這樣既可以節(jié)省用戶的帶寬,又可以將 fetch 返回的 Promise 置為 rejected 的狀態(tài),以防止任何無(wú)效的代碼更新頁(yè)面導(dǎo)致出現(xiàn)無(wú)效非法的導(dǎo)航頁(yè)面。

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 概念,代表的是導(dǎo)航頁(yè)面入口??梢酝ㄟ^(guò) navigation.currentEntry 獲取到當(dāng)前用戶所在的導(dǎo)航頁(yè)面入口,也可以通過(guò) navigation.entries() 獲取到用戶導(dǎo)航訪問(wèn)過(guò)的所有入口的列表。其中,Entry 在 Web IDL 中的規(guī)范定義如下

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:導(dǎo)航會(huì)話的 URL 地址
  • key:在導(dǎo)航會(huì)話歷史棧中的唯一標(biāo)識(shí),id 與 key 的區(qū)別在于,key 標(biāo)識(shí)是在棧中的唯一標(biāo)識(shí),id 是 NavigationHistoryEntry 實(shí)例的唯一標(biāo)識(shí)。例如:調(diào)用 replace 或 reload 時(shí)并沒(méi)有產(chǎn)生新的導(dǎo)航會(huì)話,但會(huì)生成新的 NavigationHistoryEntry,前后兩個(gè) NavigationHistoryEntry 實(shí)例的 key 相同,但 id 不同。
  • id:導(dǎo)航會(huì)話的唯一標(biāo)識(shí)
  • index:指示該導(dǎo)航會(huì)話在歷史棧的位置,默認(rèn)從 0 開(kāi)始
  • sameDocument:true 代表當(dāng)前是處于激活狀態(tài),false 則表示未激活
  • getState:返回導(dǎo)航會(huì)話存儲(chǔ)的狀態(tài),類似 history.state
  • ondispose:監(jiān)聽(tīng) dispose 事件,在該導(dǎo)航會(huì)話從歷史棧中刪除時(shí)觸發(fā)

可以通過(guò) getState() 來(lái)獲取 Entries 的 State,例如 navigation.currentEntry.getState(),這里的 State 也可以通過(guò) navigation.updateCurrentEntry({state: something}); 來(lái)更新。

?導(dǎo)航操作?

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

打開(kāi)目標(biāo)地址頁(yè)面,相等于 history.pushState 和 history.replaceState,但是支持跨域地址。

  • navigation.reload({ state: any })

刷新當(dāng)前頁(yè)面,相當(dāng)于調(diào)用了 location.reload()

  • navigation.back()

在導(dǎo)航會(huì)話歷史中向后移動(dòng)一頁(yè),相當(dāng)于 history.back()

  • navigation.forward()

在導(dǎo)航會(huì)話歷史中向前移動(dòng)一頁(yè),相當(dāng)于 history.forward()

  • navigation.traverseTo(key: string)

在導(dǎo)航會(huì)話歷史記錄中加載特定頁(yè)面,相當(dāng)于 history.go(),但區(qū)別在于傳參不同,navigation 給每個(gè)導(dǎo)航會(huì)話設(shè)置了一個(gè)唯一標(biāo)識(shí),traverseTo 接受的參數(shù)正是該唯一標(biāo)識(shí),即 NavigationHistoryEntry.key。

?不足?

新 API,兼容性不好

實(shí)際上,Navigation API 是從 Chrome 102 才開(kāi)始支持的,查了下筆者的 Chrome 版本,也才剛到 103 版本。

展望

本文所闡述的內(nèi)容,核心并不是為了詳盡詳細(xì)地介紹 Navigation 各個(gè) API 的使用細(xì)節(jié),而是想表達(dá)一下目前用 History API 來(lái)實(shí)現(xiàn) SPA 所涉及的問(wèn)題,并延伸介紹一下實(shí)現(xiàn) SPA 的更好解決方案。個(gè)人認(rèn)為,Navigation API 將有可能是未來(lái)的趨勢(shì),或許在不久的將來(lái),他將是實(shí)現(xiàn) SPA 的主要方案,而 History API 則可能更多成為一種 fallback 方案。

參考文獻(xiàn)

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

aPaaS Growth 團(tuán)隊(duì)專注在用戶可感知的、宏觀的 aPaaS 應(yīng)用的搭建流程,及租戶、應(yīng)用治理等產(chǎn)品路徑,致力于打造 aPaaS 平臺(tái)流暢的 “應(yīng)用交付” 流程和體驗(yàn),完善應(yīng)用構(gòu)建相關(guān)的生態(tài),加強(qiáng)應(yīng)用搭建的便捷性和可靠性,提升應(yīng)用的整體性能,從而助力 aPaaS 的用戶增長(zhǎng),與基礎(chǔ)團(tuán)隊(duì)一起推進(jìn) aPaaS 在企業(yè)內(nèi)外部的落地與提效。


文章名稱:MDN里暫時(shí)還查不到的NavigationAPI
網(wǎng)頁(yè)URL:http://www.dlmjj.cn/article/djpedhd.html