場景再現
眾所周知,vue-router
有三種模式 :hash
、html5
、abstract
, 一般的前端專案中會選擇hash
模式進行開發,最近做了一個運營活動就是基於vue-router的hash模式
進行開發的。
- 專案註冊了兩個路由(抽象出來的Demo)
var router = new VueRouter({
routes: [{
name: `index`,
path: ``,
component: indexcomponent
},{
name: `demo`,
path: `/demo`,
component: democomponent
}]
});
複製程式碼
-
入口頁面需要引數,所以提供URL:
https://www.xxx.com?from=weixin
, 瀏覽器裡輸入URL回車後,頁面自動增加一個#/
變為https://www.xxx.com?from=weixin#/
。 -
index頁面中一個按鈕點選後跳轉demo,同時想攜帶index中獲取的引數,看API選擇瞭如下方式,結果URL變成了:
https://www.xxx.com?from=weixin#/test?userId=123
router.push({
path: `demo`,
query: {
plan: `private`
}
})
複製程式碼
產生質疑
- URL有什麼標準?(上面Demo頁面跳轉後URL看起來怪怪的)
- vue-router是如何控制URL的?
質疑探究
URL標準
統一資源定位符(或稱統一資源定位器/定位地址、URL地址等,英語:Uniform Resource Locator,常縮寫為URL)
標準格式:scheme:[//authority]path[?query][#fragment]
例子
下圖展示了兩個 URI 例子及它們的組成部分。
hierarchical part ┌───────────────────┴─────────────────────┐ authority path ┌───────────────┴───────────────┐┌───┴────┐ abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 └┬┘ └───────┬───────┘ └────┬────┘ └┬┘ └─────────┬─────────┘ └──┬──┘ scheme user information host port query fragment urn:example:mammal:monotreme:echidna └┬┘ └──────────────┬───────────────┘ scheme path
URL中的『?』『#』
-
『?』
- 路徑與引數分隔符
- 瀏覽器只識別url中的第一個『?』,後面的會當做引數處理
-
『#』
- 『#』一般是頁面內定位用的,如我們最熟悉不過的錨點定位
- 瀏覽器可以通過『onhashchange』監聽hash的變化
- http請求中不包含#
Request Headers
中的Referer
不包含#- 改變#不觸發網頁過載
- url中#後面出現的任何字元都會被截斷。(
http://www.xxx.com/?color=#fff
發出請求是:/color=
) - 改變#會改變history
window.location.hash
讀取#值
URL讀取和操作
URL讀取和操作涉及location和history兩個物件,具體如下:
location API :
- 屬性
- href = protocol + hostName + port + pathname + search + hash
- host
- origin
- 方法
- assign
- href
- replace ,不記錄history
- reload
history API:
- 方法
- back()
- forward()
- go()
- H5新增API
- pushState()
- replaceState()
- popstate監聽變化
vue-router路由實現淺析
初始化router的時候,根據指定的mode選擇路由實現,當然mode判斷有一定邏輯和相容策略
switch (mode) {
case `history`:
this.history = new HTML5History(this, options.base)
break
case `hash`:
this.history = new HashHistory(this, options.base, this.fallback)
break
case `abstract`:
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== `production`) {
assert(false, `invalid mode: ${mode}`)
}
}
複製程式碼
我們選擇hash
模式進行深入分析,對應HashHistory
模組,該模組是history/hash.js
實現的,當被呼叫的時候,對全域性路由變化進行了監聽
window.addEventListener(supportsPushState ? `popstate` : `hashchange`, () => {
...
})
複製程式碼
同時hash.js
中也實現了push
等api方法的封裝,我們以push
為例,根據原始碼可以看出,它的實現是基於基類transitionTo
的實現,具體如下:
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
複製程式碼
既然呼叫了transitionTo
那麼來看它的實現,獲取引數後呼叫confirmTransition
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// 獲取URL中的引數
const route = this.router.match(location, this.current)
this.confirmTransition(route, () => {
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
...
})
}
複製程式碼
同時confirmTransition
裡實現了一個佇列,順序執行,iterator
通過後執行next
,進而志新pushHash()
,實現頁面hash改變,最終實現了${base}#${path}
的連線
function getUrl (path) {
const href = window.location.href
const i = href.indexOf(`#`)
const base = i >= 0 ? href.slice(0, i) : href
return `${base}#${path}`
}
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
複製程式碼
問題解決
https://www.xxx.com?from=weixin#/test?userId=123
這個頁面看起來感覺怪,是因為這個連線中幾乎包含了所有的引數,而且hash裡面還有一個問號,一個URL中多個問號的不常見- vue-router也是基於基本的URL操作來進行URL切換的,在基本基礎上進行了封裝。裡面很多思路還是應該多學習借鑑的。比如實現的佇列、繼承的運用等
總結
- 標準的URL應該是 search + hash ,不要被當下各種框架欺騙,誤以引數應該在hash後面拼接
- URL中可以有多個問號,但為了便於理解,還是儘量避免這種寫法
- 避免上面尷尬問題的一個方法是 HTML5 Histroy 模式,感興趣的同學可以關注並實踐一下
- 瞭解原理,瞭解設計模式,可以借鑑到平時開發專案中