zoukankan      html  css  js  c++  java
  • 撸一个简单的vue-router来剖析原理

    理解

    随着前端业务的发展,
    我们一般在写一个较为大型的vue项目时候,会使用到vue-router,来根据指定的url或者hash来进行内容的分发,可以达到不像服务端发送请求,就完成页面内容的切换,能够减少像服务器发送的请求,让用户进行页面跳转时候能够更快,体验更好

    疑问

    在初学vue-router的时候,一般人都会有一个印象,router-link以及router-view都是vue原生自带的标签。但是这个印象是错误的,vue-router本质上是一个vue的插件,通过Vue.use(VueRouter)来使用这个插件。router-link以及router-view也是这个插件实现的自定义标签。

    本文以开发插件的模式,撸一个vue-router插件以加深对其原理的了解

    url变化流程图解

    router流程图

    也就是说,要实现一个简单的vue-router,需要完成以下需求

    具体操作

    创建vue项目

    vue create my-vue-router
    

    由于只着重于vue-router的内容,所以先使用原本的vue-router这样只替换vue-router源码文件即可

    增加vue-router

    vue add router
    

    然后项目目录就变成了

        my-vue-router
          |- node_modules
          |- public
          |- src
              |- assets
              |- components
                  |- HellowWorld.vue
              |- router
                  |- index.js
              |- views
                  |- About.vue
                  |- Home.vue
              |- App.vue
              |- main.js
          |- .gitinore
          |- babel.config.js
          |- package.json
          |- README.md
          |- yarn.lock
    
    

    在目录中,新建一个myRouter.js的文件,来放置我们的源码

    新建自己的myRouter文件

    my-vue-router
          |- node_modules
          |- public
          |- src
              |- assets
              |- components
                  |- HellowWorld.vue
              |- router
                  |- index.js
    +             |- myRouter.js            
              |- views
                  |- About.vue
                  |- Home.vue
              |- App.vue
              |- main.js
          |- .gitinore
          |- babel.config.js
          |- package.json
          |- README.md
          |- yarn.lock
    

    切换引入文件为自己写的myRouter.js

    此时,@/src/router/index.js中的内容里,我们将导入的vue-router替换为我们的myRouter.js

      import Vue from 'vue'
    - import VueRouter from 'vue-router'
    + import VueRouter from './myRouter'
      import Home from '../views/Home.vue'
    
      Vue.use(VueRouter)
    
      const routes = [
        {
          path: '/',
          name: 'Home',
          component: Home
        },
        {
          path: '/about',
          name: 'About',
          // route level code-splitting
          // this generates a separate chunk (about.[hash].js) for this route
          // which is lazy-loaded when the route is visited.
          component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
        }
      ]
    
      const router = new VueRouter({
        mode: 'history',
        base: process.env.BASE_URL,
        routes
      })
    
      export default router
    

    这里我们可以看到,代码执行的流程为
    引入myRouter.js->配置routes对象->new VueRouter->export default 导出

    此处用到了 Vue.use()这个API

    Vue.use()

    vue中的插件,一个核心的api就是vue.use()

    安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为install 方法。install 方法调用时,会将 Vue 作为参数传入。
    该方法需要在调用 new Vue() 之前被调用。
    当 install 方法被同一个插件多次调用,插件将只会被安装一次。

    也就是说,我们在自己造的myRouter里得实现这个install方法

    需求

    1. 提供一个构造类,能够使用new VueRouter来生成实例
    2. 实现install方法
    3. 监听url变化,并双向绑定current方法
    4. 注册自定义组件router-linkrouter-view
    5. 实现用户配置的路由数组到map的转换,方便快速的查询到路由匹配的对象

    实现

    let Vue;//由于使用者肯定是使用vue.use引入的这个插件,所以代码里就不引入vue.js了,防止重复打包
    // 需求1 声明一个拥有constructor构造器的class
    class VueRouter{
        constructor(options={}){// 构造函数
            this.$options=options;// 保存配置项
            this.app = { // 声明一个拥有current的变量,已完成路由的双向绑定
                current:"/"
            }
            Vue.util.defineReactive(this.app,'current',this.app.current);//vue的拦截方法,会该值增加get拦截以收集依赖,set拦截以触发双向绑定
            this.routerMap={}; // 创建key-value模式的routerMap,便于使用key能够快速的找到即将render(渲染)的组件
            this.init(options); // 执行init方法,以完成需求3,4,5
        }
        init(options={}){
            this.bindBrowserEvents()// 绑定浏览器事件
            this.initComponent()//注册router-view及router-link组件
            this.createRouterMap(options.routes)//创建key-value模式的routerMap
        }
        createRouterMap(arr=[]){ // 创建routerMap
            arr.forEach(item => {
                this.routerMap[item.path]=item
            });
            //  处理完后routerMap格式如下
            //  this.routerMap = {
            //    '/':{
            //      path: '/',
            //      name: 'Home',
            //      component: Home
            //    },
            //    '/about':{
            //      path: '/about',
            //      name: 'About',
            //      component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
            //    }
            //  }
        }
        bindBrowserEvents(){ // hash模式监听 hashchange 方法
            window.addEventListener('load',this.onHashChange.bind(this))
            window.addEventListener('hashchange',this.onHashChange.bind(this))
        }
        initComponent(){ // 注册自定义组件RouterLink及RouterView
            Vue.component('RouterLink',{
                props: {
                    to: String
                },
                render(h) {
                    return h('a',{
                        attrs:{
                            href:'#'+this.to
                        }
                    },this.$slots.default)
                },
            })
            Vue.component('RouterView',{
                render:(h)=>{
                    const component = this.routerMap[this.app.current].component
                    return h(component)
                },
            })
        }
        onHashChange(){ // hash变化时,改变 this.app.current 
            window.location.hash = window.location.hash || '/'; // 如果hash没有值,则默认给补一个/#/
            if(this.routerMap[window.location.hash.slice(1)]){ // this.app.current = hash值
                this.app.current = window.location.hash.slice(1);
            }else{
                this.app.current = '/';
            }
    
            // 此处执行完后,则由于双向绑定,会触发routerView进行重新渲染
        }
    }
    
    // 需求2 实现install方法
    VueRouter.install = function(_Vue){
      Vue = _Vue; // 因为一定会先走install,所以将这个传入的Vue实例,保存到变量Vue中
    }
    

    注释都写在代码里啦,可以执行简单的路由双向绑定功能,有哪里有疑问可以提出互相学习

    觉得好的话,可以给我的 github点个star

  • 相关阅读:
    SpringBoot+CXF下Https调用webservice跳过安全证书的配置
    程序员的长安十二时辰:Java实现从Google oauth2.0认证调用谷歌内部api
    springboot集成activiti6.0多数据源的配置
    activiti工作流委托功能的设计和实现
    vue.js带复选框表单的增删改查
    bootstrap-treeview后台Json数据的封装及前台的显示
    你好,Spring!
    超大份线程池,干杯,兄弟!陆
    嗯!这篇多线程不错!伍
    是兄弟!就来看这篇多线程!叁
  • 原文地址:https://www.cnblogs.com/mr-xiao-han/p/12449653.html
Copyright © 2011-2022 走看看