前端路由原理及实现

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>
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值