簡易路由實現——(history路由)

前言

上篇文章講述了 簡易路由實現——(hash路由)的簡單實現,本文續講 history 路由的實現html

話很少說,先上 demo&& 源碼&& 工程文件(htmlRouter文件夾下)前端

history 路由原理

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>
複製代碼

實現 history 路由

init()

在 MDN 上,是這樣介紹 popstate 的github

當活動歷史記錄條目更改時,將觸發 popstate 事件。若是被激活的歷史記錄條目是經過對 history.pushState()的調用建立的,或者受到對 history.replaceState()的調用的影響,popstate 事件的 state 屬性包含歷史條目的狀態對象的副本。web

須要注意的是調用 history.pushState()history.replaceState() 不會觸發 popstate 事件。只有在作出瀏覽器動做時,纔會觸發該事件,如用戶點擊瀏覽器的回退按鈕(或者在 Javascript 代碼中調用 history.back() 或者 history.forward() 方法)算法

不一樣的瀏覽器在加載頁面時處理 popstate 事件的形式存在差別。頁面加載時 ChromeSafari 一般會觸發 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);
    }
}
複製代碼

refresh()

與 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';
        }
    });
}
複製代碼

back() && front()

後退前進直接調用 history 的 api 便可,此時會觸發 popstate 事件調用 refresh 方法渲染頁面

back() {
    window.history.back();
}
front() {
    window.history.forward();
}
複製代碼

push(option)

在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(option)

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();
    }
}
複製代碼

demo 測試

測試代碼就不寫了,與前文 hash 路由一致,效果以下:

可是在這裏發現一個問題,當處於某個路由時,刷新頁面,會出現下面這種狀況

一刷新就會出現404,在 vue-router官方文檔 中也有介紹,開啓 history 須要服務端支持!

當你使用 history 模式時,URL 就像正常的 url,例如 yoursite.com/user/id,也好看…

不過這種模式要玩好,還須要後臺配置支持。由於咱們的應用是個單頁客戶端應用,若是後臺沒有正確的配置,當用戶在瀏覽器直接訪問 oursite.com/user/id 就會返回 404,這就很差看了。

因此呢,你要在服務端增長一個覆蓋全部狀況的候選資源:若是 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。

以上即是《簡易路由實現》的完整內容,更多文章可進入個人專欄查看,感謝您的觀看

更多推薦

前端進階小書(advanced_front_end)

前端每日一題(daily-question)

webpack4 搭建 Vue 應用(createVue)

Canvas 進階(一)二維碼的生成與掃碼識別

Canvas 進階(二)寫一個生成帶logo的二維碼npm插件

Canvas 進階(三)ts + canvas 重寫」辨色「小遊戲

Canvas 進階(四)實現一個「刮刮樂」遊戲

Canvas 進階(五)實現圖片濾鏡效果

VUI建立日誌(一)——圖片懶加載指令的實現

VUI建立日誌(二)——防抖節流組件的實現

前端算法題目解析(一)

前端算法題目解析(二)

相關文章
相關標籤/搜索