上篇文章講述了 簡易路由實現——(hash路由)的簡單實現,本文續講 history 路由的實現html
話很少說,先上 demo&& 源碼&& 工程文件(htmlRouter文件夾下)前端
History 是 HTML5 新增的標準,對比 hash 它的展現更加優雅,但低版本 ie 還有兼容問題。vue
History 的 pushState,replacestate 方法能夠添加修改歷史記錄且不會發送新的服務器請求,所以能夠利用此特性實現前端路由跳轉。node
History 的 go ,back, forward 方法能夠實現跳轉,後退,前進功能,還有 popstate 事件能夠監聽到記錄變動。webpack
因爲 a 標籤 <a href="/monday">
會致使頁面頁面跳轉,所以頁面結構需改寫一番,使用插件方法進行路由跳轉git
<ul class="nav-list">
<li class="nav-item"><a onclick="router.push({name: 'monday'})">週一</a></li>
<li class="nav-item"><a onclick="router.push({name: 'tuesday', query: {name: 'suporka', age: '26'}})">週二</a></li>
<li class="nav-item"><a onclick="router.push({path: '/wednesday'})">週三</a></li>
<li class="nav-item"><a onclick="router.push({path: '/thursday', query: {name: 'suporka', age: '20'}})">週四</a></li>
<li class="nav-item"><a onclick="router.replace({name: 'friday'})">週五</a></li>
</ul>
複製代碼
在 MDN 上,是這樣介紹 popstate 的github
當活動歷史記錄條目更改時,將觸發 popstate 事件。若是被激活的歷史記錄條目是經過對
history.pushState()
的調用建立的,或者受到對history.replaceState()
的調用的影響,popstate 事件的 state 屬性包含歷史條目的狀態對象的副本。web須要注意的是調用
history.pushState()
或history.replaceState()
不會觸發 popstate 事件。只有在作出瀏覽器動做時,纔會觸發該事件,如用戶點擊瀏覽器的回退按鈕(或者在 Javascript 代碼中調用history.back()
或者history.forward()
方法)算法不一樣的瀏覽器在加載頁面時處理 popstate 事件的形式存在差別。頁面加載時 Chrome 和 Safari 一般會觸發 popstate 事件,但 Firefox 則不會。vue-router
所以在 history 路由的初始化方法中,須要對 popstate 和 load 事件進行監聽
export default class HistoryRouter extends RouterParent {
constructor(routerConfig) {
super(routerConfig);
}
init() {
// refresh 實現對應組件和當前路由綁定顯示
// bind(this) 傳入此實例對象,不然this指向有問題
window.addEventListener('popstate', this.refresh.bind(this), false);
window.addEventListener('load', this.refresh.bind(this), false);
}
}
複製代碼
與 hash 路由實現一致,這裏是對組件控制顯示隱藏,只不過在這裏能夠直接使用 history 的功能, 不用本身創建 routeHistory 來控制跳轉
refresh() {
let path = window.location.pathname,
currentComponentName = '',
nodeList = document.querySelectorAll('[data-component-name]');
// 找出當前路由的名稱
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].path === path) {
currentComponentName = this._routes[i].name;
break;
}
}
// 根據當前路由的名稱顯示對應的組件
nodeList.forEach(item => {
if (item.dataset.componentName === currentComponentName) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
}
複製代碼
後退前進直接調用 history 的 api 便可,此時會觸發 popstate 事件調用 refresh 方法渲染頁面
back() {
window.history.back();
}
front() {
window.history.forward();
}
複製代碼
在vue-router中,能夠經過 path, name 修改當前路由,而且能夠攜帶 query 參數 所以優先判斷 path, 若是有 path, 則直接調用 pushState 添加歷史記錄; 沒有 path, 則根據 name 從 routes 中找出 path, 再調用 pushState 添加歷史記錄。由於 history.pushState()
或 history.replaceState()
不會觸發 popstate,所以咱們須要手動調用一下 refresh 方法
push(option) {
if (option.path) {
// 綁定this指向,使函數能夠調用類的方法
pushHistory.call(this, option.path,option.query);
} else if (option.name) {
let routePath = '';
// 根據路由名稱找路由path
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].name === option.name) {
routePath = this._routes[i].path;
break;
}
}
if (!routePath) {
error('組件名稱不存在');
} else {
pushHistory.call(this, routePath, option.query);
}
}
}
// 路由跳轉
function pushHistory(routePath, query) {
let path = getTargetPath(routePath, query);
if (path !== window.location.pathname) {
window.history.pushState(path, '', path);
this.refresh();
}
}
function error(message) {
typeof console !== 'undefined' && console.error(`[html-router] ${message}`);
}
// 獲取即將跳轉的路徑
function getTargetPath(path, query) {
if (!query) return path;
let str = '';
for (let i in query) {
str += '&' + i + '=' + query[i];
}
return path + '?' + str.slice(1);
}
複製代碼
replace 和 push 的邏輯基本一致,只是調用的不是 pushState,而是 replaceState 方法。所以對 push 方法改造一下,使其兼容 replace
replace(option) {
// 表示當前處於replace
this.replaceRouter = true;
this.push(option);
}
push(option) {
if (option.path) {
pushHistory.call(this, option.path, option.query, this.replaceRouter);
} else if (option.name) {
let routePath = '';
// 根據路由名稱找路由path
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].name === option.name) {
routePath = this._routes[i].path;
break;
}
}
if (!routePath) {
error('組件名稱不存在');
} else {
pushHistory.call(this, routePath, option.query, this.replaceRouter);
}
}
}
// 改寫路由跳轉
function pushHistory(routePath, query, replace) {
let path = getTargetPath(routePath, query);
if (path !== window.location.pathname) {
if (replace) {
window.history.replaceState(path, '', path);
this.replaceRouter = false;
} else window.history.pushState(path, '', path);
this.refresh();
}
}
複製代碼
測試代碼就不寫了,與前文 hash 路由一致,效果以下:
可是在這裏發現一個問題,當處於某個路由時,刷新頁面,會出現下面這種狀況
一刷新就會出現404,在 vue-router官方文檔 中也有介紹,開啓 history 須要服務端支持!
當你使用 history 模式時,URL 就像正常的 url,例如 yoursite.com/user/id,也好看…
不過這種模式要玩好,還須要後臺配置支持。由於咱們的應用是個單頁客戶端應用,若是後臺沒有正確的配置,當用戶在瀏覽器直接訪問 oursite.com/user/id 就會返回 404,這就很差看了。
因此呢,你要在服務端增長一個覆蓋全部狀況的候選資源:若是 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。
以上即是《簡易路由實現》的完整內容,更多文章可進入個人專欄查看,感謝您的觀看
Canvas 進階(二)寫一個生成帶logo的二維碼npm插件
Canvas 進階(三)ts + canvas 重寫」辨色「小遊戲