Vue Route

219 阅读3分钟

基础用法

如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)

构建出来

1、写组件(页面)

// 纯的js写的一个最简单组件 father.js
const Children = { template: "<div>children</div>" }
const Father = { template : "<div><Children/></div>" }
export default Father

一般情况下,我们正常开发的话是这样

// Foo.vue
<template>
  <div>
    hahaha
  </div>
</template>

<script>
  export default {
    
  }
</script>

<style scoped>

</style>

2、定义路由

就是为你的页面映射路由

const routeArr = [
  {name: 'foo', path: '/Foo', component: import('./Foo'), redirect: '/', children: []},
  {name: 'father', path: '/father', component: import('./father')}
]

3、创建 router 实例

import VueRouter from 'vue-router'
const router = new VueRouter({
    routes: routeArr
})
export default router

4、挂载到Vue中,跟store一样

const vm = new Vue({
    router
})

怎么访问

link:标签访问。(路由跳转)

this.$router:访问路由器。(路由跳转)路由器顾名思义就是封装了一些方法,干一些事情的工具

this.$route:访问当前路由信息。(得到当前路由的一些信息)

<div id="app">
  <h1>Named Views</h1>
  <ul>
    <li>
      <router-link to="/">/</router-link>
    </li>
    <li>
      <router-link to="/other">/other</router-link>
    </li>
  </ul>
  // 这个情况比较特殊,需要同级展示多个视图
  // 其实一般情况下 router-view 都只有1-2个啦。很多个的话,代表你的设计就不合理了。
  <router-view class="view one"></router-view>
  <router-view class="view two" name="a"></router-view>
  <router-view class="view three" name="b"></router-view>
</div>

想象一下上面的视图设计,你该怎么去设计路由。 感受一下

this.$router.go(-1)
this.$router.push('/', query)

// 这个是动态响应路由参数
// 如:routes: [{ path: '/user/:id', component: User }]
// 展示的是 http://localhost:8081/user/123
this.$route.params.id

// 这个是你访问路由带过来的参数对象
// 在路由里面展示的是 http://localhost:8081/foo?id=123&name=tony
this.$route.query

后面详细补充routerroute的用法

路由

路由嵌套

  • /开头的嵌套路径会被当作根路径
routes: [
    { path: '/user/:name', component: User,
      children: [
        {
          // 当 /user/:name/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:name/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]

当访问 /user/foo 时,没有匹配到合适子路由,User 的出口(比如只显示 sideBar ,而不展示 content )是不会渲染任何东西的。如果你又一个 content 想默认渲染的话,可以提供一个空的子路由:

const router = new VueRouter({
  routes: [
    {
      path: '/user/:name', component: User, 
      children: [
        // UserHome 会被渲染在 User 的 <router-view> 中
        { path: '', component: UserHome },
        // ...其他子路由
      ]
    }
  ]
})

导航 :路由变化

<router-link> 创建 a 标签来定义导航链接

如果对路由配置和视图的渲染搞不清楚的话,请看这里

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

编程式导航:

router.push(location, onComplete?, onAbort?)

onComplete, onAbort 这两个回调函数可以忽略,因为在3.1.0+ 版本之后,如果支持 Promise,router.pushrouter.replace 将返回一个 Promise。 并且,后面会降到导航守卫

push 方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

// 字符串路径
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意

  1. 如果提供了 pathparams 会被忽略
  2. 且,同样的规则也适用于 router-link 组件的 to 属性。
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

router.replace(location, onComplete?, onAbort?)

与push没什么差别,不会向 history 添加新记录,替换掉当前的 history 记录。

router.go(n)

参数是一个整数,意思是在 history 记录中向前或者后退多少步。类似 window.history.go(n)

导航守卫 导航:路由变化

全局前置守卫 router.beforeEach

比如你想在这个页面加载的时候,在页面的最上面会有一个加载进度条。 你可以在这个钩子里面progress.start()

const router = new VueRouter({})

router.beforeEach((to, from, next)=>{
    //……
    next() // 一定要调用这个方法来resolve这个钩子函数,否则无法resolve,使得跳转(导航)一直处于等待状态
})

上面这个就是一个全局前置守卫,你可以创建多个全局前置守卫,当路由发生改变时,全局守卫就按照创建顺序执行。守卫是异步执行的,路由变化开始触发跳转一直处于等待中,直到所有守卫执行完next(), resolve 完。

其实嘞,一般我们也只会注册一个啦。

  • to: Route:跳转进入的路由对象
  • from: Route:正要离开的路由对象
  • next: function一定要调用
    • next(): resolve 这个钩子,所有钩子都resolve完了之后,导航状态变成,confirm
    • next(false):中断当前的导航。
    • next('/'):跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。参数允许设置诸如 router-link 的 to prop 或 router.push 中的选项。
    • next(error):如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

全局解析守卫 router.beforeResolve

与 router.beforeEach 区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。一般的话用上面那个啦。

全局后置钩子 router.afterEach

然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身。同样的拿进度条为例,你需要在这个钩子里面progress.end()

router.afterEach((to, from) => {
  // ...
})

某个路由的守卫 beforeEnter

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]

组件内的守卫

  • beforeRouteEnter
  • beforeRouteUpdata
  • beforeRouteLeave

不想说这个了,平时没怎么用得上,用得上的时候再来补充学习吧。

完整的导航解析流程

1. 导航被触发
2. 在失活的组件里调用离开守卫
3. 调用全局的 beforeEach 守卫
4. 重用的组件里调用 beforeRouteUpdate 守卫
5. 在路由配置里调用 beforeEnter
6. 解析异步路由组件
7. 在被激活的组件里调用 beforeRouteEnter
8. 调用全局的 beforeResolve 守卫
9. 导航被确认
10. 调用全局的 afterEach 钩子
11. 发 DOM 更新
12. 用创建好的实例传给 beforeRouteEnter 守卫中 next 的回调函数

路由元信息 meta

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

那怎么拿到meta呢

由于路由对象可以嵌套,所以匹配一个路由地址可能匹配多个路由对象。一个路由匹配到的所有路由对象会暴露为 $route.matched 数组里面,所以我们需要遍历 $route.matched 的路由对象得到meta字段。

例如 /foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

过渡动态效果

原来vue提供的<transition>组件,不仅仅是用在

  1. 条件渲染 (使用 v-if)
  2. 条件展示 (使用 v-show)
  3. 动态组件
  4. 组件根节点

也可以用在路由视图外层啊,如果对 transition 组件的用法不清楚的话,看这儿

<transition>
  <router-view></router-view>
</transition>

还可以基于当前路由与目标路由的变化关系,动态设置过渡效果:

<!-- 使用动态的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
  '$route' (to, from) {
    const toDepth = to.path.split('/').length
    const fromDepth = from.path.split('/').length
    this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
  }
}

路由懒加载(性能优化)

const Foo = () => import('./Foo.vue')   // webpack
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

// 也可以直接写成这样
const router = new VueRouter({
  routes: [
    { path: '/foo', component: () => import('./Foo.vue') }
  ]
})