我的前端工具集(三)页面路由工具

 

liuyuhang原创,未经允许禁止转载

 

目录

我的前端工具集

 

1、什么是路由

 

  狭义上的路由就是我们常用的路由器了,不管是何种方式连接,都是转发的功能,

  实现了信道的扩展和加密功能。

 

  广义上的路由,实际上就是一个指挥员,根据目标的性质,进行映射的工具。

  可以是交通信号灯,可以是路牌,可以是网络路由器,可以是人生导师!

 

  本文中所说的路由,是指根据配置文件的设置,在点击指定内容的时候,对指定的div加载指定html的程序。

  我叫他页面路由,这种路由在很多前端框架中已经有实现了,功能也不少,学习的人很少去纠其作用机理。

  本文以自己的视角去实现一个路由,并尝试满足其各种功能。

 

2、为什么要自己写前端路由

 

  为什么要自己写前端路由?我也不知道,总是觉得学一套语法,还不如自己实现一套语法,虽然是自己造轮子吧。

  使用别人的轮子总是有各种限制的,也无法根据需要进行功能的加减。

  何况,最主要的目的,还是学习。理论更重要!

 

3、路由在架构中的重要性

 

  路由是及其重要的,可能这个词并不是很多见,但是类似的功能的东西比较多了,举几个例子:

  Struts是一种优秀的前端路由映射工具。

  SpringMVC是一种优秀的前端路由映射工具。

  常用的了,这两种对于java程序员来说太常用了。

 

  基本的原理,都是根据url请求,解析映射,然后根据配置文件的配置,访问到固定的java类,同时将封装

  的servlet的request和response作为参数传递进去。

 

  同时,一个好的路由架构,能够成功拆分文件,将任务分解(项目经理头疼的问题)

 

  虽然当前使用很多框架或者es6已经实现了模块化编程和聚合,会这些的人要的薪水也较高!

  发挥每个人的优势才是重要的。

 

  有一说叫让外部极简(客户方便),让内部复杂(编码结构复杂)。

  在这个思想的指导下,让大部分的开发者极简,让少部分的开发者复杂,也是一种架构思路了!

 

4、我想认为路由应该有的功能与作用

 

  4.1.无刷加载页面功能:

    前端路由的核心功能了,要加载的页面直接加载,进行局部刷新,中间没有白屏。

 

  4.2.前置事件功能:

    执行某种效果之前执行的函数,这里的加载事件,是指一种AOP思想。

    主要是在路由跳转之前执行一定的函数,该函数应该会返回一个boolean,如果为true,则执行跳转。

 

  4.3.后置事件功能:

    好像大家都叫它钩子函数吧,即执行了某个功能以后,立即执行的函数。

 

  4.4.加载页面需要的js

  4.5.加载页面需要的css

 

5.路由以何种形态出现?

 

  根据4中描述的功能,前端路由应该设置的内容如下:

  ①state:跳转名称,应该是唯一的,作为一个map的key的存在,应该是一个字符串。

  ②target:要改变的div的target,应该不是唯一的,多个target名字相同,应该对多个div进行同样的改变,应该是一个字符串。

  ③template:html模板,该路由起作用的时候,应该在该div内加载该html模板。应该是一个字符串。

  ④url:加载div并不是跳转,也不是重定向,url是没有变化的,此参数只是为了改变地址(决定废弃,感觉没啥用,以前做过)

  ⑤jsArr:多个js文件的名称,执行该路由的时候,同时加载多个js文件。

  ⑥cssArr:多个css文件的名称,执行该路由的时候,同时加载多个css文件。

  ⑦before:前置事件函数名,字符串格式,返回boolean,若为true则执行跳转,若为false则执行某提示函数或donothing。

  ⑧after:后置事件函数名,字符串格式,即执行路由后,执行该函数(该函数不应该为加载的js的函数,因为可能未加载成功导致after无法执行)

  

6、细节部分的实现

 

  6.1.state

  其中state应为被点击的html元素的一个属性,页面加载的时候扫描路由配置,给state属性赋值,为state的名称。如:

  <div state="myStateName"></div>

  该功能应该配合一个listener来实现,假定配置文件中有key=“state”,value=“myStateName”,则listener应为:

  if(key=="state"){
    var temp = $("[state="+value+"]");
    if(temp.length>0){
      temp..click(function(){
        //执行路由功能
      })
    }    
  }

 

  6.2.before

  执行路由功能首先要先执行before的字符串,假定配置文件中有key=“before”,value=“test001()”,

  并且有

  function test001(str){
    console.log("test001");
    console.log(str);
  }

  //则执行before的应为:
  var flag = false;
  if(key=="before"){
    flag = eval("test001('hello test')")
  }

 

  6.3.template

  执行template加载之前,要先使用flag进行判断,假定配置文件中有key=“template”,value=“firstTemplate.html”,

  //这里下文中的loacl是获得http头,host,port等内容,暂且贴在这,自己调整
  var local = window.location.protocol + "//" + window.location.host + "/" + window.location.pathname.split("/")[1] + "/"

  if(key=="template"&&flag==true){
    $("div [target="+'myStateName'+"]").load(loacl+"firstTemplate.html");
  }

 

  6.4.jsArr

  加载js,前提是jsArr有内容。

  if (key=="jsArr"&&jsArr.length>0) {
    for (var i = 0; i <jsList.length; i++) {
      var script = document.createElement("script");
      script.setAttribute("type", "text/javascript");
      script.src = local +jsArr[i];
      document.getElementsByTagName("head")[0].appendChild(script);
    }
  }

 

  6.5.cssArr

  加载css,前提是cssArr有内容

  if (key=="cssArr"&&cssArr.length > 0) {
    for (var i = 0; i <cssArr.length; i++) {
      var link = document.createElement("link");
      link.setAttribute("rel", "stylesheet");
      link.href = local+cssArr[i];
      document.getElementsByTagName("head")[0].appendChild(link);
    }
  }

 

  6.6.after和before是相同的,不写了。

 

  6.7.以上的代码并非是实际代码,可以理解为伪代码吧!!

 

7.代码实现

 

  说明,上述举例是举例,实际代码会有更多的判断内容和不同的格式,以下文为准,自行修改!

 

  7.1.准备配置数据,自己注意格式

 

    //测试时用的是springboot,非生产环境,无pathName
    var local = window.location.protocol + "//" + window.location.host + "/";
    /**
     * 准备配置文件,可单写为js文件
     * 数组格式,每个元素是一个json,代表一个路由事件配置
     */
    var routeConfig = [
        {
            state : "firstState", //state属性值,被点击的标签,执行名为state的路由
            target : "firstTarget", //执行路由将页面注入的目标,是一个div的target属性
            template : local + "view/firstPage.html", //执行路由注入的页面,一个html文件路径,不带local
            before : "firstFunBefore('my First Fun Before Here')", //路由执行之前的函数,决定是否执行
            after : "firstFunAfter('my First Fun After Here')", //路由执行之后的函数
            jsArr : [ local + "js/first01.js", local + "js/first02.js" ], //注入路由时注入的js文件列表,不带local
            cssArr : [ local + "css/first01.css", local + "css/first02.css" ], //注入路由时注入的css文件列表,不带local
        },
        {
            state : "secondState",
            target : "secondTarget",
            template : local + "view/secondPage.html",
            before : "secondFunBefore('my Second Fun Before Here')",
            after : "secondFunAfter('my Second Fun After Here')",
            jsArr : [ local + "js/second01.js", local + "js/second02.js" ],
            cssArr : [ local + "css/second01.css", local + "css/second02.css" ],
        }
    ]

 

  7.2.路由框架代码,只是路由跳转用的代码而已

 

/**
     * 路由监听器初始化的函数,初始加载执行,每次有路由跳转后也要执行
     */
    function stateListener(routeConfig) {
        console.log(routeConfig);
        var setting = routeConfig;
        $("*[state]").unbind(); //移除state的监听器
        $("*[state]").click(function() { //注入监听器
            var state = $(this).attr("state");
            StateTo(state); //一旦点击即执行路由跳转函数
        })
    }

    /**
     * 路由跳转执行的函数
     * 传入参数为路由中的state的value
     */
    function StateTo(stateName) {
        console.log(stateName);
        //初始化变量
        var index,
            state,
            target,
            template,
            before,
            after,
            jsArr,
            cssArr,
            flag = false;
        //从配置数组中获取该元素的index
        for (var i = 0; i < routeConfig.length; i++) {
            if (stateName == routeConfig[i].state) {
                index = i;
                break;
            }
        }
        if (null != i) {
            state = routeConfig[index].state;
            target = routeConfig[index].target;
            template = routeConfig[index].template;
            before = routeConfig[index].before;
            after = routeConfig[index].after;
            jsArr = routeConfig[index].jsArr;
            cssArr = routeConfig[index].cssArr;
        }
        //检测state,target,template正确性
        if (checkEmpty(state) && checkEmpty(target) && checkEmpty(template) && $("[target = " + target + "]").length > 0) { //不为空且页面存在target
            flag = true;
        }
        //执行before并获得返回值
        flag = eval(before);
        //注入template,jsArr,cssArr
        if (flag == true) {
            //template注入
            $("div[target=" + target + "]").load(template);
            console.log("inject template : " + template);
            //js注入
            var jsArrLength = jsArr.length;
            if (jsArrLength > 0) {
                for (var i = 0; i < jsArrLength; i++) {
                    var script = document.createElement("script");
                    script.setAttribute("type", "text/javascript");
                    script.src = jsArr[i];
                    document.getElementsByTagName("head")[0].appendChild(script);
                    console.log("inject js : " + jsArr[i]);
                }
            }
            //css注入
            var cssArrLength = cssArr.length
            if (cssArrLength > 0) {
                for (var i = 0; i < cssArrLength; i++) {
                    var link = document.createElement("link");
                    link.setAttribute("rel", "stylesheet");
                    link.href = cssArr[i];
                    document.getElementsByTagName("head")[0].appendChild(link);
                    console.log("inject js : " + cssArr[i]);
                }
            }
            //如果有遮罩,全部删除,防止跳转后页面家假死,bootstrap中的遮罩...
            $(".modal-backdrop").remove();
        }
        //执行after
        if (flag == true) {
            flag = eval(after);
            stateListener(routeConfig);//重置路由监听
        }
        //==========
        //检验字符串是否为空的内部工具
        function checkEmpty(str) {
            return ('' != str && null != str && 'undefinded' != typeof str);
        }
    }

 

  7.3.准备测试的before和after函数代码

 

    //准备测试用的四个before和after函数
    function firstFunBefore(str) {
        console.log(str);
        return true; //允许路由跳转
    }
    function firstFunAfter(str) {
        console.log(str);
    }
    function secondFunBefore(str) {
        console.log(str);
        console.log("返回值为false,不执行路由!");
        return false; //不允许路由跳转
    }
    function secondFunAfter(str) {
        console.log(str);
    }

 

  7.4.初始化与测试按钮函数代码

 

    //init
    $(function() {
        stateListener(routeConfig)
    })
    
    function test(){
        StateTo("firstState");
    }

 

  7.5.html代码,需要引入jquery和bootstrap,版本没啥要求

 

<div class="row">
        <div class="col-lg-12">
            <!-- bar -->
            <div class="col-lg-2">
                <div state="firstState">
                    <span><h3>First</h3></span>
                </div>
                <div state="secondState">
                    <span><h3>Second</h3></span>
                </div>
                <div>
                    <button class="btn btn-default" onclick="test()">测试StateTo函数的按钮</button>
                </div>
            </div>
            <!-- something -->
            <div class="col-lg-3">
                <div target="firstTarget">
                    <h2>First Page</h2>
                </div>
                <div target="secondTarget">
                    <h2>Second Page</h2>
                </div>
            </div>
        </div>
    </div>

 

   7.6.测试用的html,作为template

 

  firstPage.html

 

<div>
    <h2>The First Page Injected</h2>
</div>

 

8.运行测试

 

  8.1.页面截图

 

 

  8.2.点击First后的截图

 

  8.3.点击测试按钮后的截图

 

 

   8.4.两次操作的时候,控制台的打印结果,有些内容自己去写吧!

 

 

 

  9.总结

 

    效果已经达到,js和css文件没有写,注入是能够成功的,自行去写。

    对于统一性的功能(如遮罩,如进度条,如加载错误信息,可以统一编写,直接写在这个框架内,也可以另增加init模块,

    使得这个模块在before或者fater或者其他地方选择执行,类似于AOP思想,统一编写,减少些业务的程序员的工作量。)

 

以上!