前端路由原理及实现
vue-router的原理介绍
简单概括,当使用$router.push、$router.replace时,调用window的location.href、location.hash、location.replace、location.pushState、location.replaceState方法,把浏览器的路由历史拷贝一份到vue自己的缓存中,使用window的popstate事件来监听页面路由后退变化,解析当前的url来加载对应的路由模块。
vue-router的两种模式
vue-router有两种模式(还有一种服务端的不做介绍),一种是histroy模式(就是没有“#”),一种就是hash模式(带“#”).
hash是什么?中文意思就是锚点,如果html基础还记得的话,应该知道是什么,这里给基础知识忘记了的同学简单举个例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>浏览器路由</title>
<style>
div{height: 500px; text-align: center;line-height: 500px; font-size: 40px;}
</style>
</head>
<body>
<div style="background: yellow;">
<a href="#/1f">跳转到一楼</a>
<a href="#/2f">跳转到二楼</a>
</div>
<div style="background: yellowgreen;">一楼<a name="/1f"></a></div>
<div style="backgrour nd: green;">二楼<a name="/2f"></a></div>
</body>
</html>
点击a标签可以知道,hash模式只是跳转到页面的某一块,它会改变浏览器的url,但是不会跳转到别的页面,所以操作起来会简单些。
history模式操作起来会比较复杂,因为它需要考虑服务器的配置以及前端文件存放路径。
以nginx举例,访问 localhost:80/时,其实访问的是html/index.html,当你的链接是 localhost:80/router/(注意,不带斜杠默认找的是router.html)时,其实会访问html/router/index.html,也就是说如果这个路径下没有index.html,会404,所以vue-router官网上有一个nginx配置
location / {
try_files $uri $uri/ /index.html;
}
它的意思是如果你的链接是 loaclhost:80/xxx,不管xxx是router/还是router/router1/,访问的都是html/index.html
如果你的代码不是直接放在html而是html/baseUrl,你的vue跟nginx还得另外配置,详见vue-router官网
router简单实现
为了更简单的理解,本文用hashchange来实现hash模式。
代码很简单,就不过多介绍了,直接看代码吧
1、直接上几十行代码实现一个简易的router(并不是vue的那种)
class VueRouter {
// 接收router配置以及router挂载的dom
constructor(routers, $el){
// 路由加监听
window.addEventListener('hashchange',(evt)=>{
console.log(evt); // 可以看看都有啥
let hash = location.hash.substring(1);
this.renderRouter(hash);
})
this.routers = routers;
this.$el = $el;
// 在webpack打包中的window模式下,会把router模块挂载在window下
this.routerId = 'routerId'
}
replace(path){
// 省略亿点点细节
location.replace( getRealUrl(path) )
}
// 找到对应的router后挂载到页面上
renderRouter(path) {
// 不考虑子路由,可以省略亿点点细节
this.routers.some((val)=>{
if(val.path === path ) {
this.loadrouter(val.component)
}
return val.path === path
})
}
// 将router里的内容挂载出来
async loadrouter(component) {
const chunks = window[`webpackJson_${this.routerId}`];
let chunk = null;
// 在chunk中查找component
chunks.some(item=>{
if(item[0] === component.webpackChunkName) {
chunk = item
}
return item[0] === component.webpackChunkName
})
// 没有加载的js,懒加载
if(!chunk){
await loadScript(component.url);
chunk = chunks[chunks.length-1];
}
// 替换掉router中的内容
document.querySelector(this.$el).innerHTML= chunk[1].html;
}
}
// push是js默认属性,需要重写
VueRouter.prototype.push = (path)=>{
location.href = getRealUrl(path);
}
// 将path处理为真实可用的url
var getRealUrl = (path)=>{
return `${location.href.substring(0,location.href.indexOf('#') )}#${path}`
}
// 懒加载js
var loadScript = async (url) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
const head = document.getElementsByTagName('head')[0];
head.appendChild(script);
});
};
2、使用
// webpack的打包输出,如果output设置为window,打包出来的模块就会挂载在window上
(window["webpackJson_routerId"] = window["webpackJson_router"] || []).push([
"router1", {
html: `<div style="width:100px;height:100px;background:yellowgreen" id="router1">router1<div>`,
methods: {
method_1(){},
method_2(){},
}
}
])
// 实例化
var router = new VueRouter([
{
path: '/router1',
// vue的component最终的表现是一个js文件或者是一个js模块,取决于你怎么打包
component: {
webpackChunkName: 'router1',
}
},
{
path: '/router2',
component: {
webpackChunkName: 'router2',
url: './router2.js'
}
},
], '.router')
function gotoRouter(){
router.push('/router1')
}
function replaceRouter(){
router.replace('/router2')
}
3、html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>浏览器路由</title>
<style>
.router{height: 500px; text-align: center;line-height: 500px; font-size: 40px; background: aqua;}
</style>
</head>
<body>
<div>
<button onclick="gotoRouter()">跳转到一楼</button>
<button onclick="replaceRouter()">跳转到二楼</button>
</div>
<div class="router">
</div>
</body>
<script src="./router.js"></script>
<script src="./app.js">
</script>
</html>