目前,前端中所有的MVVM框架中基本都有自己的Router组件,比如React-router或者Vue-router,主要的作用就是通过拦截url来返回相应的组件。如果我们通过原生js来实现一个类似的router,应该怎么做呢?本文将提供一个思路和完整demo,以解释其中的原理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="author" content="[email protected]">
<title>前端路由</title>
<style type="text/css">
.router_box,
#router-view {
padding: 0 20px;
background-color: gainsboro;
height: 55px;
line-height: 50px;
}
.router_box>a {
padding: 0 10px;
color: #364086;
}
.content{
border: 1px solid #a030b3;
}
</style>
</head>
<body>
<div class="router_box">
<a href="/home" class="router">主页</a>
<a href="/sort" class="router">分类</a>
<a href="/image" class="router">图片</a>
<a href="/own" class="router">我的</a>
</div>
<div class="content">
<iframe name="mainiframe" src="" id="mainiframe" scrolling="no" onload="" frameborder="0" width="100%"></iframe>
</div>
<script type="text/javascript">
(function (win, undefined) {
function Router(parames) {
if (!(this instanceof Router)) {
return new Router(arguments)
}
let router = {};
router.routes = parames.routes || [];
router.target = parames.target || "";
router.index = parames.index || "";
this.loadIndex(router);
document.querySelectorAll(".router").forEach((item, index) => {
item.addEventListener("click", function (e) {
let event = e || win.event;
event.preventDefault();
win.location.href = `#${this.getAttribute("href")}`;
}, false);
});
win.addEventListener("hashchange", function () {
this.routerLoad(router);
}.bind(this));
};
Router.prototype = {
routerLoad: router => {
let nowHash = win.location.hash;
let index = router.routes.findIndex((item, index) => {
return nowHash == ('#' + item.path);
});
document.querySelector(`${router.target}`).src = router.routes[index].component;
},
loadIndex: router => {
document.querySelector(`${router.target}`).src = router.index;
}
}
win.Router = Router;
})(window, undefined)
new Router({
target: '#mainiframe',
index: '/static/templates/home.html',
routes: [{
path: '/home',
component: '/static/templates/home.html'
},
{
path: '/sort',
component: '/static/templates/sort.html'
},
{
path: '/image',
component: '/static/templates/image.html'
},
{
path: '/own',
component: '/static/templates/own.html'
}
]
});
</script>
</body>
</html>
服务端(nodejs+express):
const express = require('express');
const path = require('path');
let app = express();
app.use('/static',express.static(path.join('public')));
app.get('/', (req, res) => {
res.sendFile(__dirname +'/index.html')
})
app.get('/router', (req, res) => {
res.sendFile(__dirname +'/router.html')
})
app.listen(2000)
我先讲讲实现个路由需要从哪几方面入手。
第一步:监听a标签,并给href里的url加锚
我们知道,一般情况下菜单栏的加载模式中,都是通过中的
href='/xxxx'
来跳转到指定的页面,所以路由的第一步就是监听到此菜单栏中的点击事件,并在点击时通过
event.preventDefault()
阻止浏览器的默认行为。阻止默认行为后,咱们就可以通过#/index
这种形式给拿到的url加锚,至于为什么要加锚,第二步中会说明。
第二步:监听hashchange事件,并在监听被触发时加载对应的页面
在第一步中我们给url加上了锚,目的就是通过hashchange
函数来监听加了锚之后的url(即hash),监听到hash的变化后,我们可以拿到点击时的url,通过调用Router(params)
时传入的params
参数来找到此文件的静态路径,然后传入到iframe 的 src中。
注意:在这个例子中我使用的iframe
来替代组件
,在实际业务情景中,你当然也可以将这个iframe换成一个主容器(之类的),然后在’component’属性中传入组件的具体内容,或者文件路径,都是可以的。
第三步:在Router调用时添加目标容器,添加首页加载
因为我在这个例子中使用的内容容器是个iframe,所以我需要考虑多个iframe嵌套下的Loader目标(target)问题,当然,首页也是需要可以设置默认加载的。
所以,在new Roter()时我们可以看到这两个参数:
1.这段代码的意义是将函数内this环境绑定到函数内,而不是window。
当然,你也可以这样:
let _this = this;
2.要注意箭头函数中this的指向(箭头函数没有自己的this)
3.如果想把这个demo完整的跑起来,需要安装node环境,然后npm install express
。