zoukankan      html  css  js  c++  java
  • 浅读Vue-Router源码记录

    vue-router源码拾遗

    但凡是使用过Vue-router的前端开发工作者,都知道Vue-router包括两种实现模式:hash和history。为了对这两种模式有更直观的认识,我选择简略阅读源码并在此记录。


    vue-router是Vue.js框架的路由插件,下面从源码入手,边看代码边看原理,由浅入深学习vue-router实现两种路由模式的方法。

    vue-router的模式参数

    模式参数:mode

    const router = new VueRouter({
        mode: 'history',
        routes: [...]
    })
    

    创建VueRouter实例的时候,直接将mode以构造函数参数的形式传入,在VueRouter类定义(src/index.js)中,使用如下

    export default class VueRouter {
        mode: string;//传入的string类型
        history: HashHistory | HTML5History | AbstractHistory; //实际调用的对象属性
        matcher: Matcher; // url正则匹配方法
        fallback: boolean; // 如果浏览器不支持,history需要回滚为hash模式
        ...
        let mode = options.mode || 'hash' //默认是hash模式
        this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
        if (this.fallback) {
          mode = 'hash' // 传入模式为history,但是浏览器不支持,则回滚
        }
        if (!inBrowser) {
          mode = 'abstract' // 不在浏览器环境下,强制设置为‘abstract’
        }
        this.mode = mode
    
        // 根据mode的值,确定要使用的类,并实例化
        switch(this.mode) {
            case 'history':
                this.history = new HTML5History(this, option.base)
                break;
            case 'hash: 
                this.history = new HashHistory(this, option.base, this.fallback)
                break;
            case 'abstract':
                this.history = new AbstractHistory(this, option.base)
                break;
            default: 
                ...
        }
        // 通过mode确定好history实例后,进行实例的初始化和监听
        init (app: any /* Vue component instance */) {
            const history = this.history
    
            // 根据history的类别执行相应的初始化操作和监听
            if (history instanceof HTML5History) {
                // 'history'模式初始化
                history.transitionTo(history.getCurrentLocation())
            } else if (history instanceof HashHistory) {
                const setupHashListener = () => {
                    history.setupListeners()
                }
                // 'hash'模式初始化
                history.transitionTo(
                    history.getCurrentLocation(),
                    setupHashListener,
                    setupHashListener
                )
            }
    
            // 添加监听
            history.listen(route => {
                this.apps.forEach((app) => {
                    app._route = route
                })
            })
        }
    }
    

    自此,基本完成了对mode字段的前期校验和后期使用,history的实例也已经初始化完成。接下来就是路由的一些基本操作,比如push(),replace(),onReady()等。
    接着以上源码往下看,以下代码只留存关键行

    onReady (cb: Function, errorCb?: Function) {
        this.history.onReady(cb, errorCb)
    }
    
    push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        // this.history会经过初始化操作,这里就会调用原生history的push方法
        this.history.push(location, resolve, reject)
        // this.history.push(location, onComplete, onAbort)
    }
    
    replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        // replace也是原生方法的调用
        this.history.replace(location, resolve, reject)
        // this.history.replace(location, onComplete, onAbort)
    }
    

    从以上代码可看出,VueRouter类中的方法可以认为是一个代理,实际是调用的具体history对象的对应方法,在init()方法中初始化时,会根据history对象具体的类别执行不同的操作。

    说了这么多,什么时候调以及如何调用HTML5History和HashHistory有了大致的了解,接下来就看看这两个类是怎么实现的


    HashHistory

    源码文件路径:src/history/hash

    原理回顾

    hash("#")符号加在URL上只是用于指示网页中的位置,#符号本身及它后边的字符称为hash,可以通过window.location.hash属性读取。

    1. hash虽然加在URL中,但是并不会被包含在http请求中,它对服务器端无用,因此改变hash并不会重载页面。
    2. hash可以添加监听事件(window.addEventListener('hashchange', fn, false))。
    3. hash每次被改动后,都会在浏览器访问历史中增加一个记录。

    hash的以上特点,就注定可以用来实现“更新视图但不重新请求页面”

    代码解读

    构造函数

    // 继承History基类
    export class HashHistory extends History {
        constructor (router: Router, base: ?string, fallback: boolean) {
            // 基类构造器
            super(router, base)
            // check history fallback deeplinking
            if (fallback && checkFallback(this.base)) {
                // 降级检查,如果降级了并且做了降级处理,直接返回
                return
            }
            ensureSlash()
        }
        function checkFallback (base) {
            // 得到除去base的真正的location值
            const location = getLocation(base)
            if (!/^/#/.test(location)) {
                // 如果不是以/#开头,就降级处理,降级为hash模式下的/#开头
                window.location.replace(cleanPath(base + '/#' + location))
                return true
            }
        }
        function ensureSlash (): boolean {
            // 获取hash
            const path = getHash()
            if (path.charAt(0) === '/') {
                // 如果是/开头,直接返回
                return true
            }
            // 不是/开头,就手动增加开头/
            replaceHash('/' + path)
            return false
        }
    }
    

    这里什么时候算降级呢?就是不支持history api的情况

    push()

    push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        // transitionTo():父类中定义用来处理路由变化的基础逻辑的方法
        this.transitionTo(
            location,
            route => {
                pushHash(route.fullPath)
                handleScroll(this.router, route, fromRoute, false)
                onComplete && onComplete(route)
            },
            onAbort
        )
    }
    
    function pushHash (path) {
        // supportsPushState:src/util/push-state.js中定义,浏览器环境并且支持pushState()方法
        if (supportsPushState) {
            pushState(getUrl(path))
        } else {
            // 不支持,直接赋值操作
            window.location.hash = path
        }
    }
    

    以上代码可看出push()方法只是对url中的hash进行了基本的赋值,那视图如何更新呢?继续看父类中的transitionTo()方法
    源码路径:src/history/base.js

    transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        // 路由匹配过程,这个后边详解
        const route = this.router.match(location, this.current)
        this.confirmTransition(
            route,
            () => {
                this.updateRoute(route)
                ...
            }
        )
    }
    updateRoute (route: Route) {
        ...
        this.cb && this.cb(route)
        ...
    }
    

    在路由改变之后,调用了History中的this.cb方法,而this.cb的定义如下:

    listen (cb: Function) {
        this.cb = cb
    }
    

    listen方法作为基础类的方法,我们不难想到,在主类index.js中history初始化之后,必然有使用的痕迹

    init (app: any /* Vue component instance */) {
        this.apps.push(app)
        history.listen(route => {
            this.apps.forEach((app) => {
                app._route = route
            })
        })
    }
    

    根据注释可以看出入参的app是Vue组件实例,但是我们知道Vue本身的组件定义中是没有有关路由内置属性_route的,VueRouter.install = install如果组件中要有这个属性,应该是在插件加载的地方,即VueRouter的install()方法里,我们F12看下

    export function install (Vue) {
        // 混入
        Vue.mixin({
            beforeCreate () {
                if (isDef(this.$options.router)) {
                    // 混入到哪个组件,this就指向哪个组件
                    this._routerRoot = this
                    // VueRouter实例
                    this._router = this.$options.router
                    // VueRouter中的init(),由于这是全局混入,所以this===Vue组件实例
                    this._router.init(this)
                    Vue.util.defineReactive(this, '_route', this._router.history.current)
                } else {
                    this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
                }
                registerInstance(this, this)
            },
            destroyed () {
                registerInstance(this)
            }
        })
        // 在Vue原型上响应式添加$router属性
        Object.defineProperty(Vue.prototype, '$router', {
            get () { return this._routerRoot._router }
        })
        // 在Vue原型上响应式添加$route属性
        Object.defineProperty(Vue.prototype, '$route', {
            get () { return this._routerRoot._route }
        })
    }
    

    Vue.mixin()全局注册了混入,影响注册后创建的每个Vue实例,在beforeCreate钩子函数中,使用Vue.util.defineReactive设置了响应式的_route属性,当路由当_route值变化,就会调用Vue实例的render()方法,更新视图。

    replace()

    replace()与push()略有不同,它不是将新路由添加到浏览器访问历史记录,而是直接替换当前路由

    // index.js
    replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
          return new Promise((resolve, reject) => {
            this.history.replace(location, resolve, reject)
          })
        } else {
          this.history.replace(location, onComplete, onAbort)
        }
    }
    
    // history/hash.js
    replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        const { current: fromRoute } = this
        this.transitionTo(
          location,
          route => {
            replaceHash(route.fullPath)
            handleScroll(this.router, route, fromRoute, false)
            onComplete && onComplete(route)
          },
          onAbort
        )
    }
    function replaceHash (path) {
        if (supportsPushState) {
            // 这个方法干啥的呢
            replaceState(getUrl(path))
        } else {
            // 这里调用原生的replace方法
            window.location.replace(getUrl(path))
        }
    }
    
    // util/push-state.js
    export function replaceState (url?: string) {
        // 这里调的和push()一样,都是走pushState()方法
        pushState(url, true)
    }
    export function pushState (url?: string, replace?: boolean) {
        const history = window.history
        if (replace) {
            // 如果是replace,进这里
            // 重写history.state进新对象,防止修改造成影响
            const stateCopy = extend({}, history.state)
            stateCopy.key = getStateKey()
            // 调用原生replaceState
            history.replaceState(stateCopy, '', url)
        } else {
            // 不是replace,调用原生的pushState
            history.pushState({ key: setStateKey(genStateKey()) }, '', url)
        }
    }
    

    地址栏监听

    以上分析的push和replace方法,是在Vue组件的逻辑代码中调用,但是在实际场景中,还可以直接对地址栏进行修改,达到改变路由的目的,这在HashHistory是如何实现的,继续去HashHistory类代码中寻找答案。可以发现有这样一个方法setupListeners(),里边的window.addEventListener()跟上文提到的HashHistory的特性2不谋而合

    setupListeners () {
        const router = this.router
        const expectScroll = router.options.scrollBehavior
        const supportsScroll = supportsPushState && expectScroll
    
        // 重点是这里面的hashchange监听事件
        window.addEventListener(
            supportsPushState ? 'popstate' : 'hashchange',
            () => {
                const current = this.current
                if (!ensureSlash()) {
                    // 如果不符合hash要求
                    return
                }
                // 这里是基类中的视图更新
                this.transitionTo(getHash(), route => {
                    if (supportsScroll) {
                        handleScroll(this.router, route, current, true)
                    }
                    if (!supportsPushState) {
                        // 直接修改地址栏,调用replaceHash方法
                        replaceHash(route.fullPath)
                    }
                })
            }
        )
    }
    

    HTML5History

    源码文件路径:src/history/html5

    源码解读

    History interface是浏览器历史记录栈提供的接口,通过back(),forward().go()等方法,实现读取浏览器历史记录栈信息的功能,进行跳转操作。从HTML5开始,提供两个新的方法:pushState()和replaceState(),这在上文已经接触过,直接打开源码src/html5.js

    push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        const { current: fromRoute } = this
        this.transitionTo(location, route => {
            //这里直接调用基层的pushState
            pushState(cleanPath(this.base + route.fullPath)) 
            handleScroll(this.router, route, fromRoute, false)
            onComplete && onComplete(route)
        }, onAbort)
    }
    
    replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
        const { current: fromRoute } = this
        this.transitionTo(location, route => {
            // 这里直接调用replaceState
            replaceState(cleanPath(this.base + route.fullPath))
            handleScroll(this.router, route, fromRoute, false)
            onComplete && onComplete(route)
        }, onAbort)
    }
    

    相较HashHistory方式,HTML5History的push和replace在route的处理上,少了赋值层,

    // hash=>pushHash
    window.location.hash = path
    
    // hash=>replaceHash
    window.location.replace(getUrl(path))
    

    而是直接调用了history中的基层方法

    地址栏监听

    // 获取路径
    const initLocation = getLocation(this.base)
    // 添加监听方法
    window.addEventListener('popstate', e => {
        const current = this.current
    
        // Avoiding first `popstate` event dispatched in some browsers but first
        // history route not updated since async guard at the same time.
        const location = getLocation(this.base)
            if (this.current === START && location === initLocation) {
                return
            }
    
        this.transitionTo(location, route => {
            if (supportsScroll) {
                handleScroll(router, route, current, true)
            }
        })
    })
    

    AbstractHistory

    源码解读

    export class AbstractHistory extends History {
        index: number
        // 用于存放路由记录的数组,仿浏览器历史记录栈
        stack: Array<Route>
    
        constructor (router: Router, base: ?string) {
            super(router, base)
            this.stack = []
            this.index = -1
        }
        // 以push为例,任何路由操作都是操作stack数组
        push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
            this.transitionTo(
                location,
                route => {
                    this.stack = this.stack.slice(0, this.index + 1).concat(route)
                    this.index++
                    onComplete && onComplete(route)
                },
                onAbort
            )
        }
        ...
    }
    

    AbstractHistory的操作方式较为简单,初始化一个仿浏览器历史记录栈的路由数组,push(),replace()等操作,都对该数组进行操作。


    路由匹配过程

    VueRouter的基本工作流程大致就如上分析,这里有个很重要的点,需要单独分析一下,就是路由的匹配过程。
    为了方便代码解读,需要先认清几个基本概念:

    1. Location:对url的结构化描述,例如:{path: '/main', query: {age: 25}, name: 'mainPage'}等
    2. rowLocation:type rowLocation = Location | string
    3. Route:表示一条路由
        export interface Route {
            path: string
            name?: string | null
            hash: string
            query: Dictionary<string | (string | null)[]>
            params: Dictionary<string>
            fullPath: string
            matched: RouteRecord[] //匹配的所有的RouteRecord对象
            redirectedFrom?: string
            meta?: any
        }
    
    1. RouteRecord:表示路由记录对象
        export interface RouteRecord {
            path: string
            regex: RegExp // 正则规则
            components: Dictionary<Component>
            instances: Dictionary<Vue> // vue实例
            name?: string
            parent?: RouteRecord
            redirect?: RedirectOption
            matchAs?: string
            meta: any
            beforeEnter?: (
                route: Route,
                redirect: (location: RawLocation) => void,
                next: () => void
            ) => any  // 钩子函数
            props:
                | boolean
                | Object
                | RoutePropsFunction
                | Dictionary<boolean | Object | RoutePropsFunction>
        }
    

    对以上类型有基本认识后,来看下matcher实现代码

    export function createMatcher (routes: Array<RouteConfig>, router: VueRouter): Matcher {
        // 这个方法往下看
        const { pathList, pathMap, nameMap } = createRouteMap(routes)
        // 动态添加路由配置
        function addRoutes (routes) {
            createRouteMap(routes, pathList, pathMap, nameMap)
        }
        // match主函数,根据传入的raw和currentRoute 计算出新的路径并返回
        function match (raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location): Route {
            // 获取路由path query hash等
            const location = normalizeLocation(raw, currentRoute, false, router)
            const { name } = location
    
            if (name) {
                // 如果传入了name参数,找到该name对应的记录
                const record = nameMap[name]
                // 没有匹配记录,则创建
                if (!record) return _createRoute(null, location)
                // 所有参数键值
                const paramNames = record.regex.keys
                    .filter(key => !key.optional)
                    .map(key => key.name)
                // 检查获取的路由参数类型,保证是object
                if (typeof location.params !== 'object') {
                    location.params = {}
                }
                // vueRouter的参数对应赋值给浏览器的路由参数
                if (currentRoute && typeof currentRoute.params === 'object') {
                    for (const key in currentRoute.params) {
                        if (!(key in location.params) && paramNames.indexOf(key) > -1) {
                            location.params[key] = currentRoute.params[key]
                        }
                    }
                }
    
                location.path = fillParams(record.path, location.params, `named route "${name}"`)
                // 创建
                return _createRoute(record, location, redirectedFrom)
            } else if (location.path) {
                // 没有name参数,传了path
                location.params = {}
                // pathList
                for (let i = 0; i < pathList.length; i++) {
                    const path = pathList[i]
                    const record = pathMap[path]
                    if (matchRoute(record.regex, location.path, location.params)) {
                        return _createRoute(record, location, redirectedFrom)
                    }
                }
            }
            // 没有找到匹配的路由,自己创建
            return _createRoute(null, location)
        }
    }
    

    在源码文件src/create-route-map中找到createRouteMap()方法,入参传了路由配置routes,返回的是三个属性:

    1. pathList:存储所有的path值
    2. pathMap:path到RouteRecord的映射关系
    3. nameMap:name到RouteRecord的映射关系

    其中的主要函数分析一下

    function addRouteRecord (
      pathList: Array<string>,
      pathMap: Dictionary<RouteRecord>,
      nameMap: Dictionary<RouteRecord>,
      route: RouteConfig,
      parent?: RouteRecord,
      matchAs?: string
    ) {
        const { path, name } = route
    
        const pathToRegexpOptions: PathToRegexpOptions =
            route.pathToRegexpOptions || {}
        // path格式清理,确保格式正确
        const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
    
        const record: RouteRecord = {
            path: normalizedPath,
            regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
            components: route.components || { default: route.component },
            instances: {},
            name,
            parent,
            matchAs,
            redirect: route.redirect,
            beforeEnter: route.beforeEnter,
            meta: route.meta || {},
            props:
            route.props == null
                ? {}
                : route.components
                ? route.props
                : { default: route.props }
        }
        // 嵌套路由递归调用
        if (route.children) {
            route.children.forEach(child => {
            const childMatchAs = matchAs
                ? cleanPath(`${matchAs}/${child.path}`)
                : undefined
            addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
            })
        }
        // 记录path并且创建path到RouteRecord的映射
        if (!pathMap[record.path]) {
            pathList.push(record.path)
            pathMap[record.path] = record
        }
    
        // 如果路由对象存在别名,将别名递归调用,进行映射的创建
        if (route.alias !== undefined) {
            const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]
            for (let i = 0; i < aliases.length; ++i) {
                const alias = aliases[i]
    
                const aliasRoute = {
                    path: alias,
                    children: route.children
                }
                addRouteRecord(
                    pathList,
                    pathMap,
                    nameMap,
                    aliasRoute,
                    parent,
                    record.path || '/' // matchAs
                )
            }
        }
        // 创建name 到 RouteRecord 的映射
        if (name) {
            if (!nameMap[name]) {
                nameMap[name] = record
            }
        }
    }
    

    Vue-router组件分析

    routerLink作为点击跳转的组件,要实现的也是类似标签的能力

    props: {
        to: {
          type: toTypes,
          required: true
        }, // 跳转路径
        tag: {
          type: String,
          default: 'a'
        }, // 渲染出的标签类型,默认是<a>
        exact: Boolean,
        append: Boolean,
        replace: Boolean, // 调用replace还是push
        activeClass: String, // 激活后的class
        exactActiveClass: String,
        ariaCurrentValue: {
          type: String,
          default: 'page'
        },
        event: {
          type: eventTypes,
          default: 'click'
        } // 可以触发导航的事件,默认是click,还可以是'mouseover' 等
    },
    // 主要方法,render()
    render (h: Function) {
        const router = this.$router
        const current = this.$route
        const { location, route, href } = router.resolve(
          this.to,
          current,
          this.append
        )
    
        const classes = {}
        // ...这里省去一些赋值初始化语句
    
        // handler方法用于绑定
        // 绑定后,点击触发
        const handler = e => {
          if (guardEvent(e)) {
            if (this.replace) {
                //replace逻辑,触发VueRouter的replace()更新路由
                router.replace(location, noop)
            } else {
                // 添加逻辑,触发VueRouter的push()更新路由
                router.push(location, noop)
            }
          }
        }
        // 绑定click事件,忽略所有默认的点击事件
        const on = { click: guardEvent }
        if (Array.isArray(this.event)) {
            // 如果传了event为数组类型
            this.event.forEach(e => {
                on[e] = handler
            })
        } else {
            on[this.event] = handler
        }
        // 赋值初始化操作,创建元素需要附加的数据
        const data: any = { class: classes }
    
        if (this.tag === 'a') {
            // 如果是<a>元素,直接绑定
            data.on = on
            data.attrs = { href, 'aria-current': ariaCurrentValue }
        } else {
          // 找到第一个<a>标签
          const a = findAnchor(this.$slots.default)
          if (a) {
            // 为<a>标签绑定事件
            a.isStatic = false
            const aData = (a.data = extend({}, a.data))
            aData.on = aData.on || {}
            // transform existing events in both objects into arrays so we can push later
            for (const event in aData.on) {
              const handler = aData.on[event]
              if (event in on) {
                aData.on[event] = Array.isArray(handler) ? handler : [handler]
              }
            }
            // append new listeners for router-link
            for (const event in on) {
              if (event in aData.on) {
                // on[event] is always a function
                aData.on[event].push(on[event])
              } else {
                aData.on[event] = handler
              }
            }
            // 标签的所有属性=>attrs
            const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
            // 赋值href属性
            aAttrs.href = href
            aAttrs['aria-current'] = ariaCurrentValue
          } else {
            // 没有<a>标签,就为当前元素绑定
            data.on = on
          }
        }
        // 创建Vnode $createElement
        return h(this.tag, data, this.$slots.default)
      }
    }
    // click点击事件自定义
    function guardEvent (e) {
        // 忽略带有功能键的点击
        if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
        // 忽略点击引起的默认动作
        if (e.defaultPrevented) return
        // 忽略鼠标右键方法
        if (e.button !== undefined && e.button !== 0) return
        // 忽略 `target="_blank"` 属性
        if (e.currentTarget && e.currentTarget.getAttribute) {
            const target = e.currentTarget.getAttribute('target')
            if (/_blank/i.test(target)) return
        }
        // 阻止默认行为,防止跳转
        if (e.preventDefault) {
            e.preventDefault()
        }
        return true
    }
    function findAnchor (children) {
        if (children) {
            let child
            for (let i = 0; i < children.length; i++) {
                child = children[i]
                if (child.tag === 'a') {
                    return child
                }
                if (child.children && (child = findAnchor(child.children))) {
                    return child
                }
            }
        }
    }
    

    router-view

    作为视图显示组件,要根据routerLink的点击结果显示对应的Vue组件视图,还要可实现层级嵌套展示

    render (_, { props, children, parent, data }) {
        // props: props
        // children: 所有子节点
        // parent: 父组件的引用
        // data: 要创建的组件的属性
        
        data.routerView = true
    
        // 父组件的$createElement函数
        const h = parent.$createElement
        const name = props.name
        // 父组件(当前)的路由对象
        const route = parent.$route
        // 父组件(当前)的路由缓存信息
        const cache = parent._routerViewCache || (parent._routerViewCache = {})
    
        // 嵌套的层级深度
        let depth = 0
        // 是否keepAlive内
        let inactive = false
        while (parent && parent._routerRoot !== parent) {
            // 如果父组件存在,并且父组件还存在父组件,就获取父组件的Vnode的data,没有就{}
            const vnodeData = parent.$vnode ? parent.$vnode.data : {}
            if (vnodeData.routerView) {
                // 父组件routerView存在,层级+1
                depth++
            }
            if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {
                // 如果设置了keepAlive
                inactive = true
            }
            // 向上一级
            parent = parent.$parent
        }
        // 赋值嵌套层级
        data.routerViewDepth = depth
    
        // 设置keepAlive
        if (inactive) {
          // 获取当前缓存
          const cachedData = cache[name]
          // 获取当前缓存的Vue组件
          const cachedComponent = cachedData && cachedData.component
          if (cachedComponent) {
            // 创建缓存的组件
            return h(cachedComponent, data, children)
          } else {
            // 没找到组件就创建空组件
            return h()
          }
        }
        // 根据嵌套层级获取当前要显示的路由对象
        const matched = route.matched[depth]
        // 要显示的组件
        const component = matched && matched.components[name]
    
        // 为空 将当前cache设为null,请创建空组件
        if (!matched || !component) {
          cache[name] = null
          return h()
        }
    
        cache[name] = { component }
    
        // attach instance registration hook
        // this will be called in the instance's injected lifecycle hooks
        data.registerRouteInstance = (vm, val) => {
          // val could be undefined for unregistration
          const current = matched.instances[name]
          if (
            (val && current !== vm) ||
            (!val && current === vm)
          ) {
            matched.instances[name] = val
          }
        }
    
        // also register instance in prepatch hook
        // in case the same component instance is reused across different routes
        ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {
          matched.instances[name] = vnode.componentInstance
        }
    
        // register instance in init hook
        // in case kept-alive component be actived when routes changed
        data.hook.init = (vnode) => {
          if (vnode.data.keepAlive &&
            vnode.componentInstance &&
            vnode.componentInstance !== matched.instances[name]
          ) {
            matched.instances[name] = vnode.componentInstance
          }
        }
    
        const configProps = matched.props && matched.props[name]
        // save route and configProps in cachce
        if (configProps) {
          extend(cache[name], {
            route,
            configProps
          })
          fillPropsinData(component, data, route, configProps)
        }
        // 最后创建组件
        return h(component, data, children)
      }
    

    总结

    vue-router源码大致先记录在这里,仅作为学习记录,供以后回头查看。
    如有不妥之处,还请指正。
    好好学习,认真笔记
  • 相关阅读:
    Android开发学习之路-使用Handler和Message更新UI
    Android开发学习之路-Service和Activity的通信
    Android开发学习之路-自定义ListView(继承BaseAdapter)
    URI、URL、URN
    理解 node.js 的事件循环
    创建hexo风格的markdown页面
    heroku
    js通过沿着作用域链还是原型链查找变量
    浏览器中实现3D全景浏览
    数据可视化图表ECharts
  • 原文地址:https://www.cnblogs.com/xuanyuandai/p/14785044.html
Copyright © 2011-2022 走看看