VUE系列之李南江写VUE-Router源码

一 VueRouter本质


根据"不同的hash值"或者"不同的路径地址", 将不同的内容渲染到router-view中
所以实现VueRouter的核心关键点就在于如何监听'hash'或'路径'的变化, 再将不同的内容写到router-view中

模拟页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div id="html"></div>

<!-- <a onclick="go('/home')">首页</a>
<a onclick="go('/about')">关于</a>
<div id="html"></div> -->


<script>

    // history模式需要注意的两个方法
    function go(path) {
        // console.log(path);
        //这个方法可以改变浏览器路径,但是不刷新页面
        history.pushState(null, null, path);
       
        document.querySelector('#html').innerHTML = path;
    }
    //解决点击 前进或后退按钮不响应的问题
    window.addEventListener('popstate', ()=>{
        console.log('点击了前进或者后退', location.pathname);
        document.querySelector('#html').innerHTML = location.pathname;
    })
    


    // hash模式需要注意的两个方法
    window.addEventListener('hashchange', ()=>{
        // console.log('当前的hash值发生了变化');
        let currentHash = location.hash.slice(1); //#/home
        document.querySelector('#html').innerHTML = currentHash;
    })
    //解决哈希页面首次运行 不改变值的问题
    window.addEventListener('load', ()=>{
        let currentHash = location.hash.slice(1);
        document.querySelector('#html').innerHTML = currentHash;
    })
     
</script>
</body>
</html>

二 提取路由信息:

常规调用模式:

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

router/index.js

import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from './Nue-Router'


import Home from '../views/Home.vue'
import About from '../views/About.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/home',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

const router = new VueRouter({
  mode: 'history', // #/home /home
  base: process.env.BASE_URL,
  routes
})

export default router

Nue-Router.js

class NueRouter {
    constructor(options){
        this.mode = options.mode || 'hash';
        this.routes = options.routes || [];
        // 提取路由信息
        /*
        {
            '/home': Home,
            '/about': About
        }
        * */
        this.routesMap = this.createRoutesMap();
        console.log(this.routesMap);
    }
    createRoutesMap(){
        return  this.routes.reduce((map, route)=>{
            map[route.path] = route.component;
            return map;
        }, {})
    }
}
NueRouter.install = (Vue, options)=>{

}
export default NueRouter;

三 初始化路由信息

class NueRouteInfo {
    constructor(){
        this.currentPath = null;
    }
}
class NueRouter {
    constructor(options){
        this.mode = options.mode || 'hash';
        this.routes = options.routes || [];
        // 提取路由信息
        /*
        {
            '/home': Home,
            '/about': About
        }
        * */
        this.routesMap = this.createRoutesMap();
       
        // console.log(this.routesMap);
        this.routeInfo = new NueRouteInfo();
      
        // 初始化默认的路由信息
        this.initDefault();
    }
    initDefault(){
        if(this.mode === 'hash'){
            // 1.判断打开的界面有没有hash, 如果没有就跳转到#/

            if(!location.hash){
                location.hash = '/';
            }
            // 2.加载完成之后和hash发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                this.routeInfo.currentPath = location.hash.slice(1);
            });
            window.addEventListener('hashchange', ()=>{
                this.routeInfo.currentPath = location.hash.slice(1);
                console.log(this.routeInfo);
            });
        }else{
            // 1.判断打开的界面有没有路径, 如果没有就跳转到/
            if(!location.pathname){
                location.pathname = '/';
            }
            // 2.加载完成之后和history发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                this.routeInfo.currentPath = location.pathname;
            });
            window.addEventListener('popstate', ()=>{
                this.routeInfo.currentPath = location.pathname;
                console.log(this.routeInfo);
            });
        }
    }
    createRoutesMap(){
        return  this.routes.reduce((map, route)=>{
            map[route.path] = route.component;
            return map;
        }, {})
    }
}
NueRouter.install = (Vue, options)=>{

}
export default NueRouter;

举例使用:

<template>
  <div id="app">
    <!--
    <div id="nav">
      <router-link to="/home">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view></router-view>
    -->
    <a href="#/home">首页111</a>
    <a href="#/about">关于222</a>
  </div>
</template>
<!--
1.Vue-Router本质
根据"不同的hash值"或者"不同的路径地址", 将不同的内容渲染到router-view中
所以实现VueRouter的核心关键点就在于如何监听'hash'或'路径'的变化, 再将不同的内容写到router-view中
-->
<style>
</style>

四 注入全局属性,得到$router和$route

import router from "./index";

class NueRouteInfo {
    constructor(){
        this.currentPath = null;
    }
}
class NueRouter {
    constructor(options){
        this.mode = options.mode || 'hash';
        this.routes = options.routes || [];
        // 提取路由信息
        this.routesMap = this.createRoutesMap();
        this.routeInfo = new NueRouteInfo();
        // 初始化默认的路由信息
        this.initDefault();
    }
    initDefault(){
        if(this.mode === 'hash'){
            // 1.判断打开的界面有没有hash, 如果没有就跳转到#/
            if(!location.hash){
                location.hash = '/';
            }
            // 2.加载完成之后和hash发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                this.routeInfo.currentPath = location.hash.slice(1);
            });
            window.addEventListener('hashchange', ()=>{
                this.routeInfo.currentPath = location.hash.slice(1);
                console.log(this.routeInfo);
            });
        }else{
            // 1.判断打开的界面有没有路径, 如果没有就跳转到/
            if(!location.pathname){
                location.pathname = '/';
            }
            // 2.加载完成之后和history发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                this.routeInfo.currentPath = location.pathname;
            });
            window.addEventListener('popstate', ()=>{
                this.routeInfo.currentPath = location.pathname;
                console.log(this.routeInfo);
            });
        }
    }
    createRoutesMap(){
        return  this.routes.reduce((map, route)=>{
            map[route.path] = route.component;
            return map;
        }, {})
    }
}
NueRouter.install = (Vue, options)=>{
    Vue.mixin({
        beforeCreate(){
            //根组件在main.js中创建,已经有了router
            if(this.$options && this.$options.router){
                this.$router = this.$options.router;
                this.$route = this.$router.routeInfo;
            }else{
                //子组件由父组件传递
                this.$router = this.$parent.$router;
                this.$route = this.$router.routeInfo;
            }
        }
    });
}
export default NueRouter;

五 自定义路由组件 router-link

import router from "./index";

class NueRouteInfo {
    constructor(){
        this.currentPath = null;
    }
}
class NueRouter {
    constructor(options){
        this.mode = options.mode || 'hash';
        this.routes = options.routes || [];
        // 提取路由信息
        this.routesMap = this.createRoutesMap();
        this.routeInfo = new NueRouteInfo();
        // 初始化默认的路由信息
        this.initDefault();
    }
    initDefault(){
        if(this.mode === 'hash'){
            // 1.判断打开的界面有没有hash, 如果没有就跳转到#/
            if(!location.hash){
                location.hash = '/';
            }
            // 2.加载完成之后和hash发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                this.routeInfo.currentPath = location.hash.slice(1);
            });
            window.addEventListener('hashchange', ()=>{
                this.routeInfo.currentPath = location.hash.slice(1);
                console.log(this.routeInfo);
            });
        }else{
            // 1.判断打开的界面有没有路径, 如果没有就跳转到/
            if(!location.pathname){
                location.pathname = '/';
            }
            // 2.加载完成之后和history发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                this.routeInfo.currentPath = location.pathname;
            });
            window.addEventListener('popstate', ()=>{
                this.routeInfo.currentPath = location.pathname;
                console.log(this.routeInfo);
            });
        }
    }
    createRoutesMap(){
        return  this.routes.reduce((map, route)=>{
            map[route.path] = route.component;
            return map;
        }, {})
    }
}
NueRouter.install = (Vue, options)=>{
    Vue.mixin({
        beforeCreate(){
            if(this.$options && this.$options.router){
                this.$router = this.$options.router;
                this.$route = this.$router.routeInfo;
            }else{
                this.$router = this.$parent.$router;
                this.$route = this.$router.routeInfo;
            }
        }
    });
    /*
    只要外界使用了Vue-Router, 那么我们就必须提供两个自定义的组件给外界使用
    只要外界通过Vue.use注册了Vue-Router, 就代表外界使用了Vue-Router
    只要接通通过Vue.use注册了Vue-Router, 就会调用插件的install方法
    所以我们只需要在install方法中注册两个全局组件给外界使用即可
    * */
    Vue.component('router-link', {
        props: {
            to: String
        },
        render(){
            /*
            注意点: render方法中的this并不是当前实例对象, 而是一个代理对象
                    如果我们想拿到当前实例对象, 那么可以通过this._self获取
this._self代表这个router-link 组件 *
*/ // console.log(this._self.$router.mode); let path = this.to; if(this._self.$router.mode === 'hash'){ path = '#' + path; } return <a href={path}>{this.$slots.default}</a> } }); } export default NueRouter;

六 自定义路由出口组件 router-view

但是标红的一定要注意:这个方法会在router-view渲染之后 才执行,这样就无法拿到currentPath,所以把

$router 的属性变为 双向绑定,一修改(this.routeInfo属于$router的一个属性),页面就会重新渲染,也就是会让router-view重新渲染,从而拿到最新的currentPath

import router from "./index";

class NueRouteInfo {
    constructor(){
        this.currentPath = null;
    }
}
class NueRouter {
    constructor(options){
        this.mode = options.mode || 'hash';
        this.routes = options.routes || [];
        // 提取路由信息
        this.routesMap = this.createRoutesMap();
        this.routeInfo = new NueRouteInfo();
        // 初始化默认的路由信息
        this.initDefault();
    }
    initDefault(){
        if(this.mode === 'hash'){
            // 1.判断打开的界面有没有hash, 如果没有就跳转到#/
            if(!location.hash){
                location.hash = '/';
            }
            // 2.加载完成之后和hash发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                console.log("BBBBBBB");
                this.routeInfo.currentPath = location.hash.slice(1);
            });
            window.addEventListener('hashchange', ()=>{
                console.log("CCCCCCC");
                this.routeInfo.currentPath = location.hash.slice(1);
                console.log(this.routeInfo);
            });
        }else{
            // 1.判断打开的界面有没有路径, 如果没有就跳转到/
            if(!location.pathname){
                location.pathname = '/';
            }
            // 2.加载完成之后和history发生变化之后都需要保存当前的地址
            window.addEventListener('load', ()=>{
                console.log('load');
                this.routeInfo.currentPath = location.pathname;
            });
            window.addEventListener('popstate', ()=>{
                this.routeInfo.currentPath = location.pathname;
                console.log(this.routeInfo);
            });
        }
    }
    createRoutesMap(){
        return  this.routes.reduce((map, route)=>{
            map[route.path] = route.component;
            return map;
        }, {})
    }
}
NueRouter.install = (Vue, options)=>{
    Vue.mixin({
        beforeCreate(){
            if(this.$options && this.$options.router){
                this.$router = this.$options.router;
                this.$route = this.$router.routeInfo;
                /*这里 把this.$router 的所有属性都设置为双向绑定,这样当里面的属性改变
                之后,会立即进行页面刷新,load方法会执行,得到currentPath,这样router-view
                在渲染的时候,就会拿到正确的子组件 */
                Vue.util.defineReactive(this, 'xxx', this.$router);
           
            }else{
                this.$router = this.$parent.$router;
                this.$route = this.$router.routeInfo;
            }
        }
    });
    /*
    只要外界使用了Vue-Router, 那么我们就必须提供两个自定义的组件给外界使用
    只要外界通过Vue.use注册了Vue-Router, 就代表外界使用了Vue-Router
    只要接通通过Vue.use注册了Vue-Router, 就会调用插件的install方法
    所以我们只需要在install方法中注册两个全局组件给外界使用即可
    * */
    Vue.component('router-link', {
        props: {
            to: String
        },
        render(){
            /*
            注意点: render方法中的this并不是当前实例对象, 而是一个代理对象
                    如果我们想拿到当前实例对象, 那么可以通过this._self获取
            * */
            // console.log(this._self.$router.mode);
            let path = this.to;
            if(this._self.$router.mode === 'hash'){
                path = '#' + path;
            }
            return <a href={path}>{this.$slots.default}</a>
        }
    });
    Vue.component('router-view', {
        render(h){
            // console.log('render');
            let routesMap = this._self.$router.routesMap;
            let currentPath = this._self.$route.currentPath;
            // console.log(currentPath);
            let currentComponent = routesMap[currentPath];

            console.log("AAAAAA",currentPath);
            return h(currentComponent);
        }
    });
}
export default NueRouter;

猜你喜欢

转载自www.cnblogs.com/xiaonanxia/p/12442470.html
今日推荐