美文网首页程序员
Hash路由原理及案例

Hash路由原理及案例

作者: docman | 来源:发表于2019-12-29 21:26 被阅读0次

    一、前端路由

      前端路由往往是根据用户请求路径的不同,返回不同的页面或数据。其映射函数通常是进行一些DOM的显示和隐藏操作。这样,当访问不同的路径的时候,会显示不同的页面组件。

      前端路由一般都是用来做单页面开发(SPA),前端路由用户请求的路径发生改变后页面是不会刷新的,用户体验(web App)比较好。

    图片来源pixabay,基于CC0协议

    1. 路由的底层原理:

    • hash路由

      通过 onhashchange 来检测hash值的变换,通过hash值的变化来显示不同的页面。

    • history路由

      通过 pushStatereplaceState 来检测地址的改变,切换不同的页面。

    本文主要介绍hash路由的实现原理。

    环境:webpack

    (回头有时间单独整理一篇关于webpack搭建的文章)

     

    2. 先看效果再看源码~

    不要在意页面丑不丑,请注意地址栏的hash值的变化

    hash路由演示

     

    二、搭建路由

    1. 美化路径

      为了方便代码后期修改和他人理解,在刚开始的时候,在webpack的配置文件中对路径设置别名,美化一下~

    module.exports = {
        ...
        resolve: {
            ...
            // 别名的配置
            alias: {
                "@": path.join(__dirname, "../src"),
                "view": path.join(__dirname, "../src/view"),
                "controller": path.join(__dirname, "../src/controller"),
                "lib": path.join(__dirname, "../src/lib"),
                "router": path.join(__dirname, "../src/router")
            },
            ...
        },
        ...
    }
    

     

    2. 配置路由表

    • 目的:将用户请求的路径与页面进行相关联
    • 这里就像是设置了一个接口文档,明确将会传入的路由配置后,就可以开始构造路由表的路由文件了。
    import HarveyRouter from "lib/router";
    import Home from "controller/home"; // 主页 home
    import List from "controller/list"; // 页面 list
    const options = {
        mode: "hash", // 声明使用的是hash路由
        routes: [
            {
                path: "/",
                template: Home
            },
            {
                path: "/list",
                template: List
            }
        ]
    }
    const router = new HarveyRouter(options);
    
    // 方便在后面调用
    window.router = router;
    
    export default router;
    

     

    3. 构造路由

    在这一步中,主要进行以下4步操作:

    • 初始化URL
    • 监听路由变化
    • 处理路由表
    • 渲染相应的页面
    class HarveyRouter {
        constructor(options) {
            // 获取参数中的路由的形式和路由的配置
            this.$options = options;
            this.$mode = this.$options.mode || "hash";
            this.$routes = this.$options.routes || [];
            this.current = "/";
            this.mapRoutes = {};
    
            // 初始化
            this.init();
        }
        init() {
            // 初始化url
            this.initUrl();
            // 监听路由变化
            this.routeEvent();
            // 获取路由表对象
            this.mapRoutesEvent();
            // 渲染对应的页面
            this.renderTemplate();
        }
        // 初始化url
        initUrl() {
            let arr = location.href.split("#/");
            location.href = arr[0] + "#/" + arr[1];
        }
        // 监听路由变化
        routeEvent() {
            window.addEventListener("load", this.routeChangeHandler.bind(this));
            window.addEventListener("hashchange", this.routeChangeHandler.bind(this));
        }
        routeChangeHandler(e) {
            let hash = location.hash.split("?")[0].slice(1) || "/";
            this.current = hash;
            // 获取?后的请求内容
            this.getQuery();
            // 改变页面内容
            this.renderTemplate();
        }
        // 获取路由表对象
        mapRoutesEvent() {
            this.$routes.forEach(item => {
                this.mapRoutes[item.path] = item;
            })
        }
        // 渲染对应的页面
        renderTemplate() {
            let template = this.mapRoutes[this.current].template;
            template.render();
        }
        // 切换hash后跳至对应页面
        jumpPage(path) {
            location.hash = path;
        }
        // 获取传递的参数
        getQuery() {
            let obj = location.href.substr(location.href.indexOf("?") + 1).split("&").reduce(function (prev, item) {
                let key = item.split("=")[0];
                let val = item.split("=")[1];
                prev[key] = val;
                return prev;
            }, {})
            this.$query = obj;
        }
    }
    export default HarveyRouter;
    

     

    4. 简单配置页面动态更新的部分

    完成路由配置后,这里利用了MVC的原理对页面进行渲染:

    简单写了个页面

     这里用到了 art-template 前端模板引擎,创建模板页面

    art-template 官方文档:http://aui.github.io/art-template/zh-cn/docs/api.html

    <!-- home.art -->
    <div>home页面</div>
    
    <!-- list.art -->
    <div>list页面</div>
    

     

    5. 最后把html、css和js写一写,搞定!

    • index.html
    <!-- index.html -->
    
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Harvey</title>
    </head>
    
    <body>
        <header></header>
        <div class="change_page">
            <span id="home">home</span>
            <span id="list">list</span>
        </div>
        <div id="app"></div>
        <footer></footer>
    </body>
    
    </html>
    

     

    • main.js
    // main.js
    
    import "zepto";
    import "./router";
    import "./scss/index.scss"
    import header from "view/header.art";
    import footer from "view/footer.art";
    
    var pre;
    init();
    
    // 初始化
    function init() {
        addPublic();
        addEvent();
        let hash = location.hash.split("#/")[1];
        pre = hash ? $("#" + location.hash.split("#/")[1]) : $("#home");
        changePre(pre);
    }
    
    // 添加头尾
    function addPublic() {
        $("header").html(header({ str: "Hello World" }));
        $("footer").html(footer({ str: "This is Harvey's demo page." }));
    }
    
    // 添加点击事件
    function addEvent() {
        $("#home").click(function () {
            window.router.jumpPage("/");
            changePre($("#home"));
        })
        $("#list").click(function () {
            window.router.jumpPage("/list");
            changePre($("#list"));
        })
    }
    
    // 给当前点击的按钮加active
    function changePre(elem) {
        if (pre) pre.removeClass("active");
        pre = elem;
        pre.addClass("active");
    }
    

     

    • index.scss
    /* index.scss */
    
    $color:orange;
    
    * {
        margin: 0;
        padding: 0;
    }
    
    header,footer {
        width: 100%;
        height: 50px;
        text-align: center;
        background: #fff;
        line-height: 50px;
    }
    
    header {
        border-bottom: 1px solid #cccccc;
        position: sticky;
        top: 0;
    }
    
    footer {
        border-top: 1px solid #cccccc;
        position: fixed;
        bottom: 0;
    }
    
    #app {
        margin: 0 auto;
        text-align: center;
        padding: 150px 0;
        font-size: 30px;
    }
    
    .change_page {
        display: flex;
        justify-content: space-around;
        background-color: $color;
        opacity: .6;
    
        >span {
            display: block;
            width: 50%;
            text-align: center;
            padding: 20px 0;
            font-size: 20px;
            font-weight: 700;
    
            &.active {
                border-bottom: 2px solid red;
            }
        }
    }
    

     


    如果有什么不准确的地方,欢迎私信或留言指出,共同进步~~
    See you guys next time ~

     

    相关文章

      网友评论

        本文标题:Hash路由原理及案例

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