美文网首页前端热门技术JavaScript 进阶营
自己动手实现一个前端路由

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

作者: noahlam | 来源:发表于2018-06-29 16:17 被阅读0次

    前言

    用过现代前端框架的同学,对前端路由一定不陌生, 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上还有其他一些关于前端的教程和组件,有兴趣的童鞋可以看看,你们的支持就是我最大的动力。

    相关文章

      网友评论

        本文标题:自己动手实现一个前端路由

        本文链接:https://www.haomeiwen.com/subject/namayftx.html