前端路由原理及实现

vue-router的原理介绍

简单概括,当使用$router.push、$router.replace时,调用window的location.href、location.replace方法,使用window的hashchage、statechange事件来监听页面路由变化,根据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官网

vue-router简单实现

为了更简单的理解,本文只介绍hash模式的实现。history模式原理跟hash模式差不多,一个是监听hashchange,一个监听statechange。完整代码地址
代码很简单,就不过多介绍了,直接看代码吧
1、直接上几十行代码实现一个简易的vue-router

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>

猜你喜欢

转载自blog.csdn.net/qq_38217940/article/details/123973074