Vue -Router简析其核心

303 阅读1分钟

Vue -Router简析其核心

官网Vue-router

  1. .Use 插件机制
  2. 解析routes map { path: rule }
  3. hash-change (history api)
  4. $router的混入 beforeCreate(钩子)
  5. 全局组件的实现

插件机制-$Router混入

VueRouter.install = function(_Vue) {
    Vue = _Vue;
    Vue.mixin({u
        beforeCreate() {
            if (this.$options.router) { // 只有根组件才会有router属性
                Vue.prototype.$router = this.$options.router;
            }
        }
    })
}

// install ==> use(CM) 被要求
// mixin ==> 混入: 对象合并 --- 创建组件实例的时候,会合并执行

解析routes选项

  1. 事件监听
  2. 创建路由映射
  3. 创建组件

绑定事件

window.addEventListener('hashchange', this.onHashChange.bind(this), false)
window.addEventListener('load', this.onHashChange.bind(this), false)
onHashChange = () => { this.currentPath = window.location.hash.slice(1) || '/' }

创建路由映射

this.$options.routes.forEach(item => {
    this.routerMap[item.path] = item.component;
})

全局组件

// 创建了全局组件
function renderComponent() {
    Vue.component('router-link', {
        props: { to: {type: String, required: true} },
        render = h => {
            return h('a', { attrs: {href: this.to}}, [this.$options.default])
            // return <a href={this.to}>{this.$slots.default}</a> jsx写法
        }
    })
    Vue.component('router-view', {
        render = h => {
            const component = this.routerMap[this.currentPath].component;
            return h(component);
     }
    })
}

源码部分

// install 的实现
import View from './components/view'
import Link from './components/link'

export let _Vue

export function install (Vue) {
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v => v !== undefined

  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  // 混入
  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        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)
    }
  })

  // 注册拦截
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  // 全局组件的挂载
  Vue.component('RouterView', View) 
  Vue.component('RouterLink', Link)

  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  // 注册 router 的一些 hooks 进入路由 离开路由 路由更新 等
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

// history api
import { inBrowser } from './dom'
import { saveScrollPosition } from './scroll'
import { genStateKey, setStateKey, getStateKey } from './state-key'
import { extend } from './misc'

export const supportsPushState =
  inBrowser &&
  (function () {
    const ua = window.navigator.userAgent
    /* 浏览器支持
    Chrome Safari Firefox Opera IE Android iOS
    31+     7.1+ 34+     11.50+ 10+ 4.3+ 7.1+ */
    if (
      (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
      ua.indexOf('Mobile Safari') !== -1 &&
      ua.indexOf('Chrome') === -1 &&
      ua.indexOf('Windows Phone') === -1
    ) {
      return false
    }

    return window.history && typeof window.history.pushState === 'function'
  })()

export function pushState (url?: string, replace?: boolean) {
  saveScrollPosition()
  // try...catch the pushState call to get around Safari
  // DOM Exception 18 where it limits to 100 pushState calls
  const history = window.history
  try {
    if (replace) {
      // preserve existing history state as it could be overriden by the user
      const stateCopy = extend({}, history.state)
      stateCopy.key = getStateKey()
      history.replaceState(stateCopy, '', url)
    } else {
      history.pushState({ key: setStateKey(genStateKey()) }, '', url)
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url)
  }
}

export function replaceState (url?: string) {
  pushState(url, true)
}

/* @RouterLink */

import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
import { extend } from '../util/misc'
import { normalizeLocation } from '../util/location'
import { warn } from '../util/warn'

// work around weird flow bug
const toTypes: Array<Function> = [String, Object]
const eventTypes: Array<Function> = [String, Array]

const noop = () => {}

export default {
  name: 'RouterLink',
  props: {
    to: {
      type: toTypes,
      required: true
    },
    tag: {
      type: String,
      default: 'a'
    },
    exact: Boolean,
    append: Boolean,
    replace: Boolean,
    activeClass: String,
    exactActiveClass: String,
    ariaCurrentValue: {
      type: String,
      default: 'page'
    },
    event: {
      type: eventTypes,
      default: 'click'
    }
  },
  render (h: Function) {
    //  .... 省略 属性挂载 , 事件, 还是有 className 激活判断等 
    return h(this.tag, data, this.$slots.default)
  }
}

function guardEvent (e) { ... }

function findAnchor (children) {...}

/* RouterView */
import { warn } from '../util/warn'
import { extend } from '../util/misc'

export default {
  name: 'RouterView',
  functional: true,
  props: {
    name: {
      type: String,
      default: 'default'
    }
  },
  render (_, { props, children, parent, data }) {
    // used by devtools to display a router-view badge
    data.routerView = true

    // directly use parent context's createElement() function
    // so that components rendered by router-view can resolve named slots
    const h = parent.$createElement // h函数时源码 VDOM 核心
    const name = props.name
    const route = parent.$route
    const cache = parent._routerViewCache || (parent._routerViewCache = {}) // 缓存
 
    // ... 省略 

    return h(component, data, children)
  }
}

function fillPropsinData (component, data, route, configProps) {...}

function resolveProps (route, config) {...}

本文使用 mdnice 排版