v-router

v-router的作用就是根据不同的路径映射到不同的视图。

一、例子

 <div id="app">
     <h1>hello App!</h1>
     <p>
         <!--使用router-link组件来导航-->
         <!--通过传入to属性来指定连接-->
         <!--<router-link>默认会被渲染成一个<a>标签-->
         <router-link to="/foo">Go to Foo</router-link>
         <router-link to="/bar">Go to Bar</router-link>
     </p>
     <!--路由出口-->
     <!--路由匹配到组件将渲染在这里-->
     <router-view></router-view>
 </div>
</body>
<script src="vue.js"></script>
<script src="vue-router.js"></script>
<script>
    //0.如果使用模块化编程,导入Vue和VueRouter,要调用Vue.use(VueRouter)
    //1.定义(路由)组件
    const Foo = {template:'<div>foo</div>'};
    const Bar = {template:'<div>bar</div>'};
    //2.定义路由
    //每一个路由应该映射一个组件。其中'component'可以是通过Vue.extend()创建的组件构造器,或者是一个组件配置对象。
    const routes = [
        {path:'/foo',component:Foo},
        {path:'/bar',component:Bar}
    ];
    //3.创建路由实例,然后传'routes'配置
    const router = new VueRouter({
        routes:routes
    });
    //4.创建和挂载根实例
    //记得要通过router配置参数注入路由,从而让整个应用都有路由功能
    const app = new Vue({
        router:router
    }).$mount('#app');
</script>复制代码

二、路由注册

Vue通用插件注册原理

Vue提供了Vue.use的全局API来注册这些插件。


Vue.use接收一个plugin参数,维护一个installedPlugins数组,它存储所有注册过的plugin;接着会判断plugin有没有定义install方法,如果有则调用该方法,并且该方法执行的第一个参数是Vue,这样不需要从外导入import Vue;最后把plugin存储到installedPlugins中。

三、路由安装

export let _Vue
export function install (Vue) {
  if (install.installed && _Vue === Vue) return
  install.installed = true   //为了确保install逻辑只执行一次

  _Vue = Vue                //用全局的_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-router安装的最重要一步就是利用Vue.mixin去把beforeCreate和destroyed钩子函数注入到每一个组件中
  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this     //根Vue实例
        this._router = this.$options.router   //this._router表示VueRouter的实例router,它是在new Vue的时候传入的。
        this._router.init(this)         //初始化router  在new Vue时,把router作为配置的属性传入,
        //然后调用new VueRouter返回router实例,再对router进行初始化
        //把this._router变成响应式对象
        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$route两个属性的get方法,这就是为什么我们可以在组件实例上访问this.$router以及this.$route。
  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
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}复制代码

Vue.mixin函数


四、VueRouter

export default class VueRouter {
  static install: () => void;
  static version: string;

  app: any;
  apps: Array<any>;
  ready: boolean;
  readyCbs: Array<Function>;
  options: RouterOptions;
  mode: string;
  history: HashHistory | HTML5History | AbstractHistory;
  matcher: Matcher;
  fallback: boolean;
  beforeHooks: Array<?NavigationGuard>;
  resolveHooks: Array<?NavigationGuard>;
  afterHooks: Array<?AfterNavigationHook>;
  //执行new VueRouter()的时候做了哪些事情?
  constructor (options: RouterOptions = {}) {
    this.app = null     //根Vue实例
    this.apps = []      //保存持有$options.router属性的Vue实例
    this.options = options    //保存传入的路由配置
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)   //路由匹配器

    let mode = options.mode || 'hash'
    //this.fallback表示在浏览器不支持history.pushState的情况下,根据传入的fallback配置参数,
    //决定它是否退回到hash模式
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode
    //三种类型的路由:historyhash、abstract
    //实例化路由后会返回router实例
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }
  //match方法
  match (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    return this.matcher.match(raw, current, redirectedFrom)
  }

  get currentRoute (): ?Route {
    return this.history && this.history.current
  }
  //初始化函数   传入参数为Vue实例
  init (app: any) {
    process.env.NODE_ENV !== 'production' && assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
      `before creating root instance.`
    )

    this.apps.push(app)   //然后存储到this.apps中

    if (this.app) {       
      return
    }

    this.app = app        //只有根Vue实例会保存在app中

    const history = this.history    //然后拿到当前的路由

    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
    //hash路由
      //定义了setupHashListener
      const setupHashListener = () => {
        history.setupListeners()
      }
      //执行了transitionTo方法  该调用了match函数
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

  beforeEach (fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
  }

  beforeResolve (fn: Function): Function {
    return registerHook(this.resolveHooks, fn)
  }

  afterEach (fn: Function): Function {
    return registerHook(this.afterHooks, fn)
  }

  onReady (cb: Function, errorCb?: Function) {
    this.history.onReady(cb, errorCb)
  }

  onError (errorCb: Function) {
    this.history.onError(errorCb)
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.history.push(location, onComplete, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.history.replace(location, onComplete, onAbort)
  }

  go (n: number) {
    this.history.go(n)
  }

  back () {
    this.go(-1)
  }

  forward () {
    this.go(1)
  }

  getMatchedComponents (to?: RawLocation | Route): Array<any> {
    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute
    if (!route) {
      return []
    }
    return [].concat.apply([], route.matched.map(m => {
      return Object.keys(m.components).map(key => {
        return m.components[key]
      })
    }))
  }

  resolve (
    to: RawLocation,
    current?: Route,
    append?: boolean
  ): {
    location: Location,
    route: Route,
    href: string,
    normalizedTo: Location,
    resolved: Route
  } {
    const location = normalizeLocation(
      to,
      current || this.history.current,
      append,
      this
    )
    const route = this.match(location, current)
    const fullPath = route.redirectedFrom || route.fullPath
    const base = this.history.base
    const href = createHref(base, fullPath, this.mode)
    return {
      location,
      route,
      href,
      normalizedTo: location,
      resolved: route
    }
  }

  addRoutes (routes: Array<RouteConfig>) {
    this.matcher.addRoutes(routes)
    if (this.history.current !== START) {
      this.history.transitionTo(this.history.getCurrentLocation())
    }
  }
}复制代码

五、matcher


Matcher返回了两个方法:match和addRoutes。

match方法的作用:location和route做匹配。

Location:


例子:/abc?foo=bar&baz=qux#hello,它的 path/abcquery{foo:bar,baz:qux}

Route:

Route它除了描述了类似 Loctaionpathqueryhash 这些概念,还有 matched 表示匹配到的所有的 RouteRecord。

createMatcher

export function createMatcher (

  routes: Array<RouteConfig>,   //new VueRouter返回的实例
  router: VueRouter             //用户自定义的路由配置
): Matcher {
//首先执行createRouteMap   创建一个路由映射表
  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 {
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    if (name) {
      const record = nameMap[name]
      if (process.env.NODE_ENV !== 'production') {
        warn(record, `Route with name '${name}' does not exist`)
      }
      if (!record) return _createRoute(null, location)
      const paramNames = record.regex.keys
        .filter(key => !key.optional)
        .map(key => key.name)

      if (typeof location.params !== 'object') {
        location.params = {}
      }

      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]
          }
        }
      }

      if (record) {
        location.path = fillParams(record.path, location.params, `named route "${name}"`)
        return _createRoute(record, location, redirectedFrom)
      }
    } else if (location.path) {
      location.params = {}
      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)
  }

  // ...

  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    return createRoute(record, location, redirectedFrom, router)
  }

  return {
    match,
    addRoutes
  }
}复制代码


createRouteMap 函数的目标是把用户的路由配置转换成一张路由映射表。它的创建是通过遍历 routes 为每一个 route 执行 addRouteRecord 方法生成一条记录。

addRoutes

addRoutes的作用是动态添加路由配置。


六、逻辑图


v-root是vue的一款插件,它是通过Vue.use方法注册路由插件的。

在Vue.use(VueRouter)阶段,调用Vue.use中的install方法进行路由安装。install方法中有两个钩子函数:beforeCreate和destroyed;另外install中还定义了router和route,这样使得可以在程序中访问this.$router和this.$route;install中还定义了两个全局的API,rootLink和rootView。

BeforeCreate钩子函数发生在new Vue()阶段,其参数为new VueRouter()返回的参数。然后调用init函数初始化路由,调用defineReactive将其变为响应式对象。

VueRouter中定义了三中路由:hash、history、abstract。

以hash路由为例:执行transitionTo路由切换,然后进行路由匹配match。路由匹配方法它是定义在Matcher类中的,该类中主要定义了createMatcher方法、addRoutes方法、和match方法。createMatcher方法是用来把用户的路由配置转换成一张路由映射表,addRoutes方法的作用是动态添加路由配置,match放法的作用是根据raw和currentrouter计算出一个新的路径。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值