我的前端工具集(三)页面路由工具
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思想,统一编写,减少些业务的程序员的工作量。)
以上!
更多专业前端知识,请上 【猿2048】www.mk2048.com
转载:https://blog.csdn.net/qq_45670012/article/details/102486264