我的前端工具集(三)页面路由工具
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思想,统一编写,减少些业务的程序员的工作量。)
以上!