本身動手實現一個前端路由

前前言

最近工做比較忙,寫的比較慢,當我寫完這一篇準備發佈的時候,發現掘金恰好也有一篇關於前端路由的,並且寫的比我詳細,不過辛辛苦苦寫的總不能刪掉吧,再說個人路由風格是純我的思路實現的,因此仍是硬着頭皮發了哈。javascript

前言

用過現代前端框架的同窗,對前端路由必定不陌生, vue, react, angular 都有本身的 router, 那麼你對 router 的工做原理了解嗎? 若是還不瞭解, 那麼請跟我一塊兒來手寫一個簡單的前端路由, 順便了解一下.css

實現路由的2種方式

  1. hash模式
  2. history模式

缺點:html

hash:醜(地址欄要多一個#), 某些特殊場景有坑 ,如微信支付回調前端

history: 兼容性較差, 直接訪問會400 ,除非後端或者服務器有作處理vue

基本原理

hash是基於 監聽 hashchange 事件實現的,history 是基於 pushState 和 popState,java

實現

因爲history兼容性較差,並且實現方式基本沒多大區別,本文就以hash模式來實現,history的實現方式只實現不一樣的部分。react

<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<input type="button" onclick="hashPush('index')" value="goto index">
</body>
<script> // 頁面跳轉 function hashPush(url) { location.hash = "#" + url } // 監聽hash的變更 window.addEventListener('hashchange', function (e) { console.log('當前的hash地址', location.hash.slice(1) || "/") }) </script>
</html>
複製代碼

以上就是hash模式的最簡單實現,查看控制檯,能夠看到不論是點擊跳轉按鈕,仍是點擊瀏覽器的前進/後退按鈕,控制檯都會輸出當前頁面對應的 'url',有了 'url',咱們就能夠對內容作條件渲染了git

咱們在上面的代碼的基礎上,稍做修改一下,增長2個div,一個是login,一個是indexgithub

<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style> body{ margin: 0; padding: 0; } #login, #index{ width: 100%; height: 100%; display: none; } #login{ background: #f5f5f5; display: block; } #index{ background: #aaddff; } </style>
</head>
<body>

<!--登陸頁-->
<div id="login" class="component">
    <input type="button" onclick="hashPush('index')" value="登錄">
</div>

<!--首頁-->
<div id="index" class="component">
    <input type="button" onclick="hashPush('login')" value="退出登錄">
</div>

</body>
<script> // 頁面跳轉 function hashPush(url) { location.hash = "#" + url } // 監聽hash的變更 window.addEventListener('hashchange', function (e) { let url = location.hash.slice(1) || "/" console.log('當前的hash地址', url) setVisible(url) }) // 顯示跟路由地址對應的內容,隱藏其餘內容 function setVisible(url) { let components = Array.from(document.body.querySelectorAll(".component")) components.map(item => { if(item.id===url) { console.log('顯示',item.id) item.style.display = 'block' } else { console.log('隱藏',item.id) item.style.display = 'none' } }) } </script>
</html>

複製代碼

這樣,咱們就能根據不一樣的hash地址,顯示不一樣的內容,是否是已經有點路由的味道了呢?可是目前還沒法傳參,傳參的方式有不少種,最多見最通俗的,應該是query string 了吧? query string 其實很簡單,解析url便可。vuex

// 監聽hash的變更
    window.addEventListener('hashchange', function (e) {
        let url = location.hash.slice(1) || "index"
        // 解析url
        let questionIndex = url.indexOf("?")
        let path, query
        if(questionIndex >= 0){
            path = url.substr(0,questionIndex)
            let queryString = url.substr(questionIndex+1)
            let queryArray = queryString.split("&")
            let queryObject = {}
            queryArray.map(str => {
                let equalIndex = str.indexOf("=")
                if(equalIndex > 0) {
                    let key = str.substr(0,equalIndex)
                    let value = str.substr(equalIndex+1)
                    queryObject[key] = value
                }
            })
            query = queryObject
        } else {
            path = url
            query = {}
        }
        
        console.log('接收到的參數', query)
        setVisible(path)
    })
複製代碼

這樣,咱們就能獲取到URL上傳遞的query string , 還順便解決了傳參會致使路由解析不正確的bug.

傳參其實還有個更簡單的方法,就是 設置一個全局變量 param,而後在須要傳值的時候,直接對param 賦值便可

// 設置一個全局變量
    var param = {}
複製代碼
// 頁面跳轉
    // 這個多了個參數 args
    function hashPush(url, args) {
        location.hash = "#" + url
        param = args
    }
複製代碼

因爲是全局變量,因此能夠在任意位置使用 param,不過這樣直接使用全局變量的方式,也有它的弱點,就是點擊返回按鈕的時候沒法保存變量,而query string 由於是在 url 裏面的,因此返回的時候,能夠拿到上個頁面的 query string, 那咱們有沒有辦法讓全局變量的方式也能保存上一個頁面的參數呢? 咱們來稍微修改一下代碼

// 設置一個全局變量
    var param = {}
複製代碼
// 頁面跳轉
    // 這個多了個參數 args
    function hashPush(url, args) {
        location.hash = "#" + url
        // 若是有傳args參數,就在 param 對象下建一個名字跟當前url同樣的對象,並把args賦值給它
        if(args) {
            param[url] = args
        }
    }
複製代碼

那咱們在 hashchange 的時候,能夠根據url來定位該頁面的參數。

// 監聽hash的變更
    window.addEventListener('hashchange', function (e) {
        // 省略其餘代碼
        args = param[path] || {}
        // 省略其餘代碼
    })
複製代碼

這樣,咱們就初步實現了一個hash路由,那麼接下來,咱們來看看history路由怎麼實現。

history 模式

history 模式主要依靠 調用 history.pushState() 方法 和 監聽 popstate 事件。

history.pushState() 方法接收3個參數:

  1. 要傳遞的數據(參數)
  2. 給頁面設置的標題(兼容性差,幾乎沒用)
  3. url

咱們看看調用實例

history.pushState({id:1}, '我是頁面標題', url)
複製代碼

須要注意的是 pushState 的時候,並不會觸發 popstate 事件,只有在前進後退的時候,纔會觸發,因此 pushState 以後,須要手動去設置頁面的相關狀態。如上面的例子,咱們須要這樣作

history.pushState({id:1}, '我是頁面標題', url)
    setVisible(url)
複製代碼

而後監聽 popstate 事件,捕獲 前進/後退

window.addEventListener('popstate',function (e) {
     // e.state 就是你 pushState 的時候,傳的第一個參數
     let state = e.state || {}  
     let url = state.target.location.pathName
     // 根據參數 作一些其餘操做
  })
複製代碼

最後

本文最終實現代碼已經放在 github上,想要直接看效果的同窗,能夠上去直接copy,運行。

若是以爲本文對您有用,請給本文的github加個star,萬分感謝

另外,github上還有其餘一些關於前端的教程和組件,有興趣的童鞋能夠看看,大家的支持就是我最大的動力。


其餘連接

  1. 用proxy實現一個更優雅的vue
  2. 3行代碼實現一個簡易版promise
  3. vuex其實超簡單,只需3步
  4. vuex其實超簡單,喝完這3步,還有3步
  5. 從零開始徒手擼一個vue的toast彈窗組件
  6. JS中的繼承(上)
  7. JS中的繼承(下)
  8. JS中的原型對象
  9. JS中建立對象的方法
  10. moment過重? 那就試試miment--一個超輕量級的js時間庫
  11. 美團小程序框架mpvue入門教程
  12. 美團小程序框架mpvue(花名:沒朋友)蹲坑指南
  13. 一個基於mpvue的toast彈窗組件mptoast
相關文章
相關標籤/搜索