vue-router二

142 阅读3分钟

嵌套子路由

作用:一个页面可以通过嵌套子路由展示不同的组件的内容

<template>
    <div class="hello">
        <h1>{{ msg }}</h1>
        <!-- 添加子路由导航 -->
        <p>导航 :
            <router-link to="/home">首页</router-link> |
            <router-link to="/home/one">-子页面1</router-link> |
            <router-link to="/home/two">-子页面2</router-link>
        </p>
        <!-- 子页面展示部分 -->
        <router-view/>
    </div>
</template>

<script>
export default {
    name: 'Home',
    data () {
        return {
            msg: 'Home Page!'
        }
    }
}
</script>

<style scoped>
</style>


one.vue/two.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: "One",
  data() {
    return {
      msg: "Welcome to One/Two!"
    };
  }
};
</script>


<style scoped>

</style>

index.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import One from '@/components/One'
import Two from '@/components/Two'

Vue.use(Router)

export default new Router({
    routes: [
    {
        path: '/', // 默认页面重定向到主页
        redirect: '/home'
    },
    {
        path: '/home', // 主页路由
        name: 'Home',
        component: Home,
        children:[ // 嵌套子路由
            {
                path:'one', // 子页面1
                component:One
            },
            {
                path:'two', // 子页面2
                component:Two
            },
        ]
    }
    ]
})

这样home页就能点击显示one或者two组件内容了

routers中name属性作用

用法一:

通过name属性,为一个页面中不同的router-view渲染不同的组件,可以跳转路由

<template>
  <div id="app">
     <router-view></router-view>
     <router-view  name="Foo"></router-view> //将渲染Foo组件
     <router-view  name="Bar"></router-view>   //将渲染Bar组件
  </div>
</template>

用法二

用name传参,使用$route.name获取组件name值

 <template>
  <div id="app">
    <p>{{ $route.name }}</p> //可以获取到渲染进来的组件的name值
    <router-view></router-view>
  </div>
</template>

用法三(路由传参)

var router = new VueRouter({
      routes: [
        { name:'register', path: '/register/:id/:name', component: register }
      ]
    })
   <router-link :to="{name:'register',params:{id:10,name:'小明'}}">注册</router-link>

Vue router的实现

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

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

new Vue({
  router
  ...
})

Vue 通过 use 方法,加载VueRouter中的 install 方法。install 完成 Vue 实例对 VueRouter 的挂载过程

export function install (Vue) {
 // ...
  // 混入 beforeCreate 钩子
  Vue.mixin({
    beforeCreate () {
      // 在option上面存在router则代表是根组件 
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        // 执行_router实例的 init 方法
        this._router.init(this)
        // 为 vue 实例定义数据劫持
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        // 非根组件则直接从父组件中获取
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })
 
  // 设置代理,当访问 this.$router 的时候,代理到 this._routerRoot._router
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })
  // 设置代理,当访问 this.$route 的时候,代理到 this._routerRoot._route
  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })
 
  // 注册 router-view 和 router-link 组件
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  // Vue钩子合并策略
  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
  // ...
}
在构造Vue实例的时候,我们会传入router对象:

new Vue({
  router
})

此时的router会被挂载到 Vue 的跟组件this.options选项中。在option上面存在router则代表是根组件。如果存在this.options选项中。在 option 上面存在 router 则代表是根组件。如果存在this.options,则对_routerRoot 和 _router进行赋值操作,之后执行 _router.init() 方法。 然后通过 registerInstance(this, this)这个方法来实现对router-view的挂载操作:

 // 执行 vm.$options._parentVnode.data.registerRouteInstance 渲染 router-view 组件
 const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

因为只有 router-view 组件定义了data.registerRouteInstance函数。data.registerRouteInstance 主要用来执行 render 的操作,创建 router-view 组件的 Vnode :

data.registerRouteInstance = (vm, val) => {
  // ...
  return h(component, data, children)
}

vue-router是通过vue.use()方法被挂载到vue的实例中,为了构造出router对象,我们还需要对VueRouter进行实例化操作,像这样:

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/', name: 'home', component: Home },
    { path: '/foo', name: 'foo', component: Foo },
    { path: '/bar/:id', name: 'bar', component: Bar }
  ]
})

constructor

VueRouter内部源码定义

export default class VueRouter {
 
  // ...
  constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode

    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 (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    return this.matcher.match(raw, current, redirectedFrom)
  }

  get currentRoute (): ?Route {
    return this.history && this.history.current
  }

  init () {}
  beforeEach () {}
  beforeResolve () {}
  afterEach () {}
  onReady () {}
  onError () {}
  push () {}
  replace () { }
  go () {}
  back () { }
  forward () { }
  getMatchedComponents () { }
  resolve ( ) { }
  addRoutes () { }
}

constructor实例化的时候:通过new VueRouter({...})创建了一个VueRouter的实例,VueRouter通过参数mode来指定路由模式:

  • 首先根据mode来确定所选的模式,如果当前情况下不支持history模式,会强制切换到hash模式
  • 如果当前环境部署浏览器环境,会切换到abstract模式,之后通过不同模式生成不同的history对象

init

在安装的时候,会执行该对象的init()方法

init (app: any /* Vue component instance */) {
    // ...
    this.apps.push(app)

    // main app already initialized.
    if (this.app) {
      return
    }

    this.app = app

    const history = this.history

    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

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

在安装的beforeCreate钩子中,通过调用实例的init()方法

this._router.init(this)

init()方法中的app变量存储当前vue实例的this,然后将app存入数组apps中,通过判断this.app()判断是否被实例化,然后通过history来确定不同路由的切换history.transitionTo(),之后通过history.listen()来注册路由变化的响应回调。