vue2 router路由源码分析(vue-router 3版本history模式) 简单版

本文详细解释了如何在Vue项目中使用VueRouter,涉及install方法、组件注册、路由信息存储、嵌套路由处理以及`router-link`和`router-view`组件的实现,确保无刷新页面导航和popstate事件处理。
摘要由CSDN通过智能技术生成

源码分析:

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

由于在使用Router时 使用了Vue.use 并且Router为一个对象所以 Router里有一个install方法

路由的本质就是地址栏的切换渲染不同的内容

首先我们先新建一个vueRouter文件夹 在这个文件夹下新建一个index.js文件 我们对外暴露一个名字为Router的class 然后在里面写一个install方法

export default class  Router{
    static install(){
            
    }
}

一 编写install 方法

install 方法是默认就加载的我们把初始化逻辑在这里写

第一步判断是否注册过插件 用变量installed来判断

第二步 把vue构造函数记录到全局变量

第三步就是混入了

想一下为什么路由在每个页面都可以使用 原因就是源码中使用了混入mixin 在beforeCreate生命周期中在每个组件实例创建之前,将路由配置添加到组件实例中,以便在组件中使用。

let _Vue = null
export default class  Router{
 static install(Vue){
    //判断是否注册过插件
    if(Router.install.installed){
        return
    }
    Router.install.installed = true
    //把vue构造函数记录到全局变量
    _Vue = Vue
    //混入
    _Vue.mixin({
        beforeCreate(){
            //这里的this.$options是在Vue 2中,this.$options对象包含了创建Vue实例时传递的选项。
            //当你在Vue实例中使用router选项来配置Vue Router时,它会被保存在this.$options对象中
            if(this.$options.router){
                 //把router挂在到_Vue原型上
                _Vue.prototype.$router = this.$options.router
            }
        }
    })
 }
 }

二 生命变量存储路由信息和当前路由

 constructor(options){
    //记录options信息
    this.options = options
    //存储路由信息
    this.routeMap = {}
    //记录当前的路由 默认是/
    this.data = _Vue.observable({
        current:'/'
    })
 }

三 初始化路由 把路由信息记录在routeMap中

 init(){
     //初始话路由
    this.createRouteMap(this.options.routes)
 }
 //这个方法里涉及到嵌套路由的遍历
 createRouteMap(routes){
    //遍历所有的路由规则包括嵌套路由 存储到routeMap中
    routes.forEach(route=>{
    // 判断当前路由是否有path和component
    if (route.path && route.component) {
        // 将path和component作为键值对存储到routeMap中
        this.routeMap[route.path] = route.component;
      }
      // 判断当前路由是否有子路由
      if (route.children && route.children.length > 0) {
        // 递归遍历子路由
        this.createRouteMap(route.children);
      }
    }) 
 }

四 我们还需要注册router-link 和router-view 两个组件

先注册router-link router-link 实质就是一个a标签 我们需要组织默认行为 router-link 跳转的时候是根据to传的参数进行的 参数可以是String或者Object类型 我们进行如下写操作

 //注册组件
 initCompontent(Vue){
    Vue.component('router-link',{
        props:{
            to:[String,Object]
        },
        render(h){
            return h('a',{
                attrs:{
                    href:this.resolveTo
                },
                on:{
                    click:this.clickHandler
                },
            },[this.$slots.default])
        },
        computed: {
            resolveTo() {
              // 判断传入的to是否是对象
              if (typeof this.to === 'object') {
                  //这里也可以返回path自行判断
                return this.to.name
              } else {
                // to不是对象,直接使用to作为href
                return this.to;
              }
            },
          },
        methods:{
            clickHandler(e){
                //阻止默认事件
                e.preventDefault()
                if (typeof this.to === 'object') {
                    for (let key in this.to) {
                        if (this.to.hasOwnProperty(key)) {
                            //这里我默认写的是name去匹配 实际逻辑要多一点 
                          history.pushState({}, '', this.to.name + this.to[key] );
                          this.$router.data.current = this.to.name;
                            return
                        }
                      }
                  }
                  //利用history.pushState去改变地址栏的路由 无刷新地向当前history插入一条历史状态
                 history.pushState({},'',this.to)
                 //渲染新的组件
                 this.$router.data.current = this.to  
            }
        }
    })
 }

这时候你会发现在使用router-link 进行跳转时 是没有问题的 但是你点击浏览器后退或者前进时 地址栏路径变了 页面却没有渲染出来

原因就是对应的页面没有update 我们还需要监听浏览器的popstate事件

新增方法 initEvent 监听popstate事件 然后从新更新页面

 initEvent(){
    window.addEventListener('popstate',()=>{
        //监听popstate事件 window.location.pathname获取路由地址复制给current从新渲染新的页面
        this.data.current = window.location.pathname
    })
 }

在init里在调用事件

 //初始化
 init(){
    this.createRouteMap(this.options.routes)
    this.initCompontent(_Vue)
    this.initEvent()
 }

接下来我们注册一下router-view组件 在initCompontent方法里面写

 //注册组件
 initCompontent(Vue){
    Vue.component('router-link',{
        props:{
            to:[String,Object]
        },
        render(h){
            return h('a',{
                attrs:{
                    href:this.resolveTo
                },
                on:{
                    click:this.clickHandler
                },
            },[this.$slots.default])
        },
        computed: {
            resolveTo() {
              // 判断传入的to是否是对象
              if (typeof this.to === 'object') {
                return this.to.name
              } else {
                // to不是对象,直接使用to作为href
                return this.to;
              }
            },
          },
        methods:{
            clickHandler(e){
                //阻止默认事件
                e.preventDefault()
                if (typeof this.to === 'object') {
                    for (let key in this.to) {
                        if (this.to.hasOwnProperty(key)) {
                          history.pushState({}, '', this.to.name + this.to[key] );
                          this.$router.data.current = this.to.name;
                            return
                        }
                      }
                  }
                 history.pushState({},'',this.to)
                 this.$router.data.current = this.to  
            }
        }
    })

    let self = this
    Vue.component('router-view',{
        render (h){
            /这里只是一个简易版本 当有多个router-view时候会出问题
            const compontent = self.routeMap[self.data.current]
            return h(compontent)
        }
    })
 }

完整版代码如下

let _Vue = null
export default class  Router{
 static install(Vue){
    //判断是否注册过插件
    if(Router.install.installed){
        return
    }
    Router.install.installed = true
    //把vue构造函数记录到全局变量
    _Vue = Vue
    //混入
    _Vue.mixin({
        beforeCreate(){
            //这里的this.$options是在Vue 2中,this.$options对象包含了创建Vue实例时传递 
            //当你在Vue实例中使用router选项来配置Vue Router时,它会被保存在this.$options对象中
            if(this.$options.router){
                 //把router挂在到_Vue原型上
                _Vue.prototype.$router = this.$options.router
                this.$options.router.init()
            }
        }
    })
 }
 constructor(options){
    //记录options信息
    this.options = options
    //存储路由信息
    this.routeMap = {}
    //记录当前的路由
    this.data = _Vue.observable({
        current:'/'
    })
 }
 //初始化
 init(){
    this.createRouteMap(this.options.routes)
    this.initCompontent(_Vue)
    this.initEvent()
 }
 createRouteMap(routes){
    //遍历所有的路由规则包括嵌套路由 存储到routeMap中
    routes.forEach(route=>{
    // 判断当前路由是否有path和component
    if (route.path && route.component) {
        // 将path和component作为键值对存储到routeMap中
        this.routeMap[route.path] = route.component;
      }
      // 判断当前路由是否有子路由
      if (route.children && route.children.length > 0) {
        // 递归遍历子路由
        this.createRouteMap(route.children);
      }
    }) 
 }
 //注册组件
 initCompontent(Vue){
    Vue.component('router-link',{
        props:{
            to:[String,Object]
        },
        render(h){
            return h('a',{
                attrs:{
                    href:this.resolveTo
                },
                on:{
                    click:this.clickHandler
                },
            },[this.$slots.default])
        },
        computed: {
            resolveTo() {
              // 判断传入的to是否是对象
              if (typeof this.to === 'object') {
                return this.to.name
              } else {
                // to不是对象,直接使用to作为href
                return this.to;
              }
            },
          },
        methods:{
            clickHandler(e){
                //阻止默认事件
                e.preventDefault()
                if (typeof this.to === 'object') {
                    for (let key in this.to) {
                        if (this.to.hasOwnProperty(key)) {
                          history.pushState({}, '', this.to.name + this.to[key] );
                          this.$router.data.current = this.to.name;
                            return
                        }
                      }
                  }
                 history.pushState({},'',this.to)
                 this.$router.data.current = this.to  
            }
        }
    })

    let self = this
    Vue.component('router-view',{
        render (h){
            const compontent = self.routeMap[self.data.current]
            return h(compontent)
        }
    })
 }
 initEvent(){
    window.addEventListener('popstate',()=>{
        this.data.current = window.location.pathname
    })
 }
}

然后引入直接能用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值