zoukankan      html  css  js  c++  java
  • 手写一个简易版的Vue-Router,包含VueRouter的基本功能

    Vue-Router本质

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

    window.addEventListener('popstate', ()=>{}) // 监听浏览器后退
    window.addEventListener('pushState', ()=>{}) // 监听浏览器前进
    // history.pushState(null, null, path)
    window.addEventListener('hashchange', ()=>{}) // 监听浏览器地址hash变化
    window.addEventListener('load', ()=>{}) // 监听浏览器首次加载
    

    提取路由信息

    src/router/ 下实现自己的router对象。

    初始化路由信息

    注入全局属性

    Vue根实例的router配置传入router实例,每个子组件中注入 this.$routerthis.$route

    this.$router 拿到的是VueRouter对象,里面包含router对象的所有信息。
    this.$route 拿到的是当前路由的信息。
    
    image-20210606214848129 image-20210606214923772

    自定义路由组件

    ​ 只要外界使用了Vue-Router, 那么我们就必须提供两个自定义的组件 router-linkrouter-view 给外界使用;只要外界通过Vue.use注册了Vue-Router, 就代表外界使用了Vue-Router;只要外界通过Vue.use注册了Vue-Router, 就会调用插件的install方法所以我们只需要在instal方法中注册两个全局组件给外界使用即可。

    自定义路由出口组件

    // src/router/Nue-Router.js
    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();
          	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);
                });
            }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;
                });
            }
        }
        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){ // 根组件,main.js中的Vue对象
                    this.$router = this.$options.router;
                    this.$route = this.$router.routeInfo;
                  	Vue.util.defineReactive(this, 'xxx', this.$router); // 让Vue双向绑定这个router对象,这样点击router-link触发路由更新的时候会触发监听事件,再次执行router-view组件的render方法。
                }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: { // router-link组件接收一个to参数
                to: String
            },
            render(){ // 告诉外界 router-link 的本质,使用router-link的时候会被替换为rende返回的内容。
                // 注意点: 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){ // 组件渲染会先执行render方法,后面执行之前注册的load方法,但是第一次执行render方法的时候currentPath还没有取到,无法渲染,这就需要让Vue双向绑定这个router对象,router变化的时候会触发更新,再次执行render方法,这个时候组件才能被正确渲染。也就是执行router-view会两次执行render方法。
                let routesMap = this._self.$router.routesMap;
                let currentPath = this._self.$route.currentPath;
                // console.log(currentPath);
                let currentComponent = routesMap[currentPath];
                return h(currentComponent);
            }
        });
    }
    export default NueRouter;
    
    // src/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', // eg: #/home mode模式:/home
      base: process.env.BASE_URL,
      routes
    })
    export default router
    
    <div id="nav">
      <router-link to="/home">首页</router-link> 
      <router-link to="/about">关于</router-link>
    </div>
    <router-view></router-view>
    

    监听路由变化

    beforeRouteUpdate

    可以监听 $route 或者 使用钩子函数 beforeRouteUpdate


    全局前置守卫

    beforeEach

    beforeEach 所有路由跳转之前执行

    const router = new VueRouter({ ... })
    
    router.beforeEach((to, from, next) => {
      // ...
    })
    

    全局解析守卫

    beforeResolve

    router.beforeResolve 导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。


    全局后置钩子

    afterEach

    router.afterEach

    router.afterEach((to, from) => {
      // ...
    })
    

    路由独享的守卫

    beforeEnter*

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
    

    组件内的守卫

    beforeRouteEnter

    beforeRouteUpdate

    beforeRouteLeave

    你可以在路由组件内直接定义以下路由导航守卫:

    const Foo = {
      template: `...`,
      beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
      },
      beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
      },
      beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
    

    完整的导航解析流程

    1、导航被触发。
    2、在失活的组件里调用 beforeRouteLeave 守卫。
    3、调用全局的 beforeEach 守卫。
    4、在重用的组件里调用 beforeRouteUpdate 守卫。
    5、在路由配置里调用 beforeEnter。
    6、解析异步路由组件。
    7、在被激活的组件里调用 beforeRouteEnter。
    8、调用全局的 beforeResolve 守卫 (2.5+)。
    9、导航被确认。
    10、调用全局的 afterEach 钩子。
    11、触发 DOM 更新。
    12、调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例vm会作为回调函数的参数传入。
    
  • 相关阅读:
    C++后台开发校招面试常见问题
    算术表达式的前缀,中缀,后缀相互转换
    Redis键值对数据库的设计与实现
    C++经典面试题(最全,面中率最高)
    C++开发工程师面试题大合集
    C++中常用的设计模式
    C++中const修饰函数,函数参数,函数返回值的作用
    C++main函数的参数介绍以及如何在main函数前执行一段代码
    Windows系统下安装tensorflow+keras深度学习环境
    第十三周学习总结
  • 原文地址:https://www.cnblogs.com/meihao1203/p/14999919.html
Copyright © 2011-2022 走看看