自己动手实现一个前端路由

前言

用过现代前端框架的同学,对前端路由一定不陌生, vue, react, angular 都有自己的 router, 那么你对 router 的工作原理了解吗?
如果还不了解, 那么请跟我一起来手写一个简单的前端路由, 顺便了解一下.

实现路由的2种方式

  1. hash模式
  2. history模式

缺点:

hash:丑(地址栏要多一个#), 某些特殊场景有坑 ,如微信支付回调

history: 兼容性较差, 直接访问会400 ,除非后端或者服务器有做处理

基本原理

hash是基于 监听 hashchange 事件实现的,history 是基于 pushState 和 popState,

实现

由于history兼容性较差,而且实现方式基本没多大区别,本文就以hash模式来实现,history的实现方式只实现不同的部分。

<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',我们就可以对内容做条件渲染了

我们在上面的代码的基础上,稍作修改一下,增加2个div,一个是login,一个是index

<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即可。

    // 监听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上还有其他一些关于前端的教程和组件,有兴趣的童鞋可以看看,你们的支持就是我最大的动力。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • /*Returns thenumberof entries in the joint sessionhistory...
    heachou阅读 636评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 原文见martin的博客 最近一直在研究前后端分离,ajax可以很好的解决前后端分离的问题,但是又存在着浏览器无法...
    small_a阅读 9,648评论 4 34
  • 慈悲|《清明·冬祭》 文/徐荷骊 太阳醒着 可她还是冰冷的 把冬的尸体抱怀里的人 总能不说话就不说 所以花静悄悄地...
    到马路对面去阅读 284评论 0 4
  • 目 录 人儿何处归(目录)上 节 人儿何处归(4) “姑姑,你也去赶集啊!”一个响亮的招呼声打断了李老...
    陈子谦阅读 161评论 0 0