vue-router原理分析与实践

vue_router_cover.png

今天大师兄跟大家简单聊聊Router的实现原理,以及我们如何去实现这样一个插件。

Vue RouterVue.js官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。
关于Vue Router的使用就不做过多介绍了,大家可以前往Vue Router官网去学习哦~

vue-router插件的基本使用

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({routes:[]})
export default router

import router from './route'
new Vue({
    render: h => h(APP),
    router
})

从上述代码可以看出,router也是作为一个插件去使用的,所以在进行原理实践时候,我们开发的就是一个插件。

插件开发思路

  • 定义一个Router类,用来进行所有的router操作。定义一个install方法,将router挂载到Vue的实例上去

  • 注册全局组件router-linkrouter-viewrouter-link组件解析为一个a标签,router-view解析为一个div标签,内容为当前路由对应的component

  • 监听hashchange事件来改变当前路由对应的component,监听load事件做同样的事情

  • 对于嵌套路由而言,在渲染router-view时候,先去判断当前router-view的深度,即当前router-view是处在哪个层级,然后在解析routes时候判断当前路由,如果当前路由和routes中某个路由都为'/'根路由,则直接放到路由渲染数组中,如果当前路由不是根路由,并且routes中的某个路由包含当前路由,则意味着routes数组中的这个路由要么是当前路由的父路由,要么就是当前路由,然后把routes数组中的这个路由放到路由渲染数组中去,放完之后如果还有childrens,递归去做就行。最后得到了一个路由匹配数组,这个数组里面包含当前路由和当前路由的父路由,并且数组中子路由的下标与之前router-view的层级下标相等,这样就能正确的将子路由的component正确的渲染到对应的router-view中去了。

譬如当前路由表如下:

routes:[
    {
        path: '/',
        component: () => import ('../views/index.vue')
    },
    {
        path: '/second',
        component: () => import ('../views/second.vue'),
        childrens: [
            {
                path: '/seconde/article',
                component: import ('../view/article.vue')
            }
        ]
    }
]

此时second组件下有一个router-view,用来渲染子路由——article组件,在app下还有一个父router-view,用来渲染index、second组件,所以此时second组件下的router-view的层级是1(初始化为0)。如果此时浏览器访问路由 /second/article 时候,触发我们的路由匹配方法,遍历routes数组和当前路由对比,当前路由不是根路由,并且包含 /second 路由,所以path为 /second 的选项被push进入路由渲染数组中,然后此路由还有childrens,进行递归,好家伙,当前路由和 /second/article 完全相等,所以也被push到了渲染数组中。最后我们得到了一个数组,包含两个路由选项,父路由下标0,子路由下标1,之前我们也将router-view做了层级标记,这样就能得到子router-view对应渲染的component了。
~nice

插件开发

先来一个cRouter文件夹,下面搞一个index.js,里面就是我们传统的router使用,上面有,然后再搞一个crouter.js:

import Link from './cLink'
import View from './cView'
var Vue

class cRouter {
  constructor(options) {
        this.$options = options
    this.courrentRoute = window.location.hash.slice(1) || '/'
    //定义一个响应式的路由渲染数组
        Vue.util.defineReactive(this,'routeMap',[])
        // 遍历匹配路由
    this.initRouterMap()
    // 初始化route改变事件
    this.initRouteChange()
  }
 
  initRouterMap(route) {
        let routes = route || this.$options.routes
        for (const routeItem of routes) {
            if (this.courrentRoute === '/' && routeItem.path === '/') {
                this.routeMap.push(routeItem)
                return
            }

            if (
            routeItem.path !== '/'
            && 
            this.courrentRoute.indexOf(routeItem.path) !== -1) {
                this.routeMap.push(routeItem)
                if (routeItem.childrens && routeItem.childrens.length > 0) {
                    this.initRouterMap(routeItem.childrens)
                }
                return
            }
        }
  }

  initRouteChange() {
    window.addEventListener('hashchange', this.routeChange.bind(this))
    window.addEventListener('load', this.routeChange.bind(this))
  }

  routeChange() {
        this.courrentRoute = window.location.hash.slice(1)
        this.routeMap = []
    this.initRouterMap()
  }
}

function install(_Vue) {
  Vue = _Vue

  Vue.mixin({
    beforeCreate() {
      if (this.$options.crouter) {
        Vue.prototype.$crouter = this.$options.crouter
      }
    },
  })

  Vue.component('router-link', Link)

  Vue.component('router-view', View)
}

export default {
  cRouter,
  install,
}

cview.js用来渲染router-view

export default {
    render(h) {
        // 将自身标记为一个router-view,避免和其他元素搞混
        this.$vnode.data.routerView = true
        let parent = this.$parent
        //默认自己层级为0
        let routeDeep = 0

        while(parent) {
            // 判断是否存在父元素并且父元素有值
            const vodeData = parent.$vnode && parent.$vnode.data
            if (vodeData) {
                // 如果父router-view是true,则自身层级增加
                if (vodeData.routerView) {
                    routeDeep++
                }
            }
            //继续寻找父元素,进行递归
            parent = parent.$parent
        }

        let component = null
        const route = this.$crouter.routeMap[routeDeep]
        if (route) {
            component = route.component
        }
        return h(component)
    }
}

cLink.js用来渲染router-link

export default {
    props: {
        to: {
            type: String,
            default: '/',
        },
    },
    render(h) {
        return h(
            'a',
            { attrs: { href: `#${this.to}` } },
            this.$slots.default
        )
    }
}

文章到这里,我们简单实现了类似vue aouter路由的功能。

大师兄想说的是:如今开源框架大大方便了我们的开发效率,但是单纯的使用三方框架并不能让我们学到更多知识,我们应该是研究去探索他的实现原理以及设计理念,去思考如果让我们设计一个框架,我们需要掌握哪些知识,该如何设计?

我想,这样的学习才能学到更多的知识~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容