本文目录
  1. SPA
  2. vue路由
  3. 入门案例
    1. 创建组件
    2. 定义路由规则
    3. main.js挂载路由
    4. app.vue展示组件和导航
    5. 总结
  4. 动态路由匹配
    1. 案例
    2. 响应路由参数的变化
    3. 通配符 (*)
    4. 匹配优先级
  5. 嵌套路由(多级路由)
  6. 命名路由
  7. 编程式导航
    1. router.push()
    2. router.replace()
    3. router.go()
  8. 命名视图
  9. 重定向和别名
    1. 重定向
    2. 别名
  10. 路由组件传参
    1. 布尔模式
    2. 对象模式
    3. 函数模式
  11. history工作模式
  12. 导航守卫
    1. 全局前置守卫
    2. 全局后置守卫
    3. 路由独享守卫
    4. 组件独享守卫
    5. activated和deactivated
    6. 完整的导航解析流程
  13. 路由元信息
  14. 路由懒加载

分类: vue | 标签: vue vue-router

vue-router笔记

发表于: 2022-07-06 21:13:28 | 字数统计: 4.7k | 阅读时长预计: 23分钟

vue-router官网:https://v3.router.vuejs.org/zh/guide/essentials/navigation.html

SPA

只有一个页面,切换页面不通过服务器刷新页面,而是通过路由和ajax。

vue路由

用 Vue.js + Vue Router 创建单页应用

入门案例

创建组件

Bar.vue

<template>
    <div>bar组件</div>
</template>

Foo.vue

<template>
    <div>foo组件</div>
</template>

定义路由规则

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Foo from '../views/Foo.vue'
import Bar from '../views/Bar.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/foo', // 匹配的路径
    component: Foo // 匹配的组件
  },
  {
    path: '/bar',
    component: Bar
  }
]
const router = new VueRouter({
  routes
})
export default router

main.js挂载路由

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,//挂载路由
  store,
  render: h => h(App)
}).$mount('#app')

app.vue展示组件和导航

<template>
  <div id="app">
    <h2>APP组件</h2>
    <!--router-link 组件来导航: 通过传入 `to` 属性指定链接
    <router-link> 默认会被渲染成一个 `<a>` 标签
      active-class:表示激活的样式
      -->
    <router-link to="/foo" active-class="active">foo</router-link>&nbsp;
    <router-link to="/bar" active-class="active">bar</router-link>
    <!-- -->
    <!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
  </div>
</template>

<style lang="less">
  .active{
    color: red;
  }
</style>

总结

1.router.js中定义路由

2.main.js中挂载路由

3.router-link定义路由导航 router-view 定义路由展示区域

4.main.js中注入路由器之后,在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由

动态路由匹配

其实就是restful风格的路径传参

案例

第一步:创建组件

<template>
    <!-- 获取路由的路径参数 $route.params -->
    <div>user组件id: {{$route.params.id}}</div>
</template>

第二步:router/index.js配置路由

import Vue from 'vue'
import VueRouter from 'vue-router'
import Foo from '../views/Foo.vue'
import Bar from '../views/Bar.vue'
import User from '../views/User.vue'

Vue.use(VueRouter)

const routes = [
  //....
  {
    path: '/user/:id',// 动态路径参数 以冒号开头
    component: User
  }
]
const router = new VueRouter({
  routes
})
export default router

第三步:app.vue中使用

<template>
  <div id="app">
    <h2>APP组件</h2>
    <!--此处传递一个666参数,对应路由的id-->
    <router-link to="/user/666" active-class="active">User</router-link>&nbsp;
    <!-- -->
    <!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
  </div>
</template>

也可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:

模式匹配路径$route.params
/user/:username/user/evan{ username: 'evan' }
/user/:username/post/:post_id/user/evan/post/123{ username: 'evan', post_id: '123' }

如果是通过?传参那么对应的值会设置到$route.query

<template>
  <div id="app">
    <h2>APP组件</h2>
    <!--通过?传参那么对应的值会设置到`$route.query`-->
    <router-link to="/user?id=1&name=tom" active-class="active">User</router-link>&nbsp;
    <!-- -->
    <!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
  </div>
</template>

响应路由参数的变化

/user/1 导航到 /user/2原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子(destroyed)不会再被调用

例如: 从666切换到777的时候,user组件的destroyed方法不会被调用

  • app.vue
<router-link to="/user/666" active-class="active">User666</router-link>&nbsp;
<router-link to="/user/777" active-class="active">User777</router-link>&nbsp;
  • user.vue
<template>
    <!-- 获取路由的路径参数 -->
    <div>user组件id: {{$route.params.id}}</div>
</template>

<script>
export default {
  destroyed () {
    console.log('User组件被销毁')
  },
  //   watch: {
  //     $route (to, from) {
  //       console.log(to, from)
  //     }
  //   },
  beforeRouteUpdate (to, from, next) {
    console.log(to, from)
    next()
  }
}
</script>

两种方式: 监听路由参数的变化作出响应

第一种:watch监听

watch: {
    $route (to, from) {
      console.log(to, from)
    }
  }

第二种:使用组件内路由守卫beforeRouteUpdate这个路由钩子

 beforeRouteUpdate (to, from, next) {
    console.log(to, from)
    next() //千万别忘了放行
  }

通配符 (*)

匹配所有。一般把这个放到最后面用来展示404错误页面

{
    path: '*',
    component: Error
  }

匹配以xxx开头

{
    path: '/user-*',
    component: Test
  }

当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分.也就是user-后面的部分

// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'

匹配优先级

路由定义得越早,优先级就越高。

嵌套路由(多级路由)

在路由中新增一个children数组,数组中的每一个对象就是子路由.

注意: 子路由的path不要写 /

router/index.js

{
    path: '/user',
    component: User,
    children: [
      {
        path: 'add',
        component: UserAdd
      },
      {
        path: 'update',
        component: UserUpdate
      }
    ]
  }

user.vue组件

<template>
    <div class="main">
      <h1>user组件</h1>
      <router-link to="/user/add">添加用户</router-link>
      <router-link to="/user/update">修改用户</router-link>
      <router-view></router-view>
    </div>
</template>

UserAdd.vue组件

<template>
  <div>新增用户组件</div>
</template>

UserUpdate.vue组件

<template>
  <div>修改用户组件</div>
</template>

命名路由

有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候

{
      path: '/user/:userId',
      name: 'user', //给路由起个名字
      component: User
    }

编程式导航

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

router.push()

想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)

声明式编程式
<router-link :to="...">router.push(...)

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

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

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

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

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

注意:如果提供了 pathparams 会被忽略,上述例子中的 query 并不属于这种情况。

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

记忆: pp不能组合(path和params)

router.replace()

router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

声明式编程式
<router-link :to="..." replace>router.replace(...)

router.go()

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default

<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: { //这里变为components
        default: Foo,
        a: Bar, //a,b为名字和router-view中的name属性值对应
        b: Baz
      }
    }
  ]
})

重定向和别名

重定向

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

别名

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,那么“别名”又是什么呢?

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

上面对应的路由配置为:

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

路由组件传参

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。使用 props 将组件和路由解耦:(简化这种写法 $route.params.id)

布尔模式

如果 props 被设置为 trueroute.params 将会被设置为组件属性(类似父组件给子组件传值)。

const User = {
  props: ['id'], //这里和父组件给子组件传值一样
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },//props设置为true

    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})

对象模式

props 是对象,它会被按原样设置为组件属性。当 props静态的时候有用。(props后对象是写死的)

const router = new VueRouter({
  routes: [
    {
      path: '/promotion/from-newsletter',
      component: Promotion,
      props: { newsletterPopup: false }
    }
  ]
})

函数模式

你可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。

const router = new VueRouter({
  routes: [
    {
      path: '/search',
      component: SearchUser,
      props: route => ({ query: route.query.q })
    }
  ]
})

URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。

请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。

history工作模式

hash模式,页面不会重新加载,带# ,url不好看

history模式, 页面会重新加载,但是需要后端配合配置,否则就报错了。url好看

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

导航守卫

导航守卫主要用来通过跳转或取消的方式守卫导航。分为:全局的, 单个路由独享的,和组件级的

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。例如:/user/1 和 /user/2

守卫说白了,类似拦截器

全局前置守卫

你可以使用 router.beforeEach 注册一个全局前置守卫:

const router = new VueRouter({ ... })

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

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto proprouter.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到 /login 的示例:

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

全局后置守卫

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

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

路由独享守卫

你可以在路由配置上直接定义 beforeEnter 守卫:

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

这些守卫与全局前置守卫的方法参数是一样的。

组件独享守卫

可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter 通过路由进入组件之前
  • beforeRouteUpdate (2.2 新增) 当前路由改变,但是该组件被复用时调用
  • beforeRouteLeave 通过路由离开组件之前
const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

activated和deactivated

当在router-view开启组件缓存的时候,组件不会调用destoryd生命周期方法,此时可以通过activated和deactivated这两个方法判断当前组件是激活状态还是失活状态。

<!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
    <keep-alive include="Foo">
      <router-view></router-view>
    </keep-alive>

Foo.vue

<template>
    <!-- <div>foo组件:{{$route.params.id}}</div> -->
    <div>foo组件:{{query}}</div>
</template>

<script>
export default {
  name: 'Foo',
  props: ['query'],
  beforeRouteUpdate (to, from, next) {
    console.log('Foo组件独享守卫--beforeRouteUpdate', to, from)
    next()
  },
  activated () { // 组件被激活了
    console.log('Foo组件activated')
  },
  deactivated () {
    console.log('Foo组件deactivated')
  }
}
</script>

完整的导航解析流程

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

路由元信息

定义路由的时候可以配置 meta 字段,那么如何访问这个 meta 字段呢?$route.matched 数组

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

下面例子展示在全局导航守卫中检查元字段:

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

路由懒加载

结合 Vue 的异步组件 (opens new window)和 Webpack 的代码分割功能 (opens new window),轻松实现路由组件的懒加载。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

webpackChunkName为打包分块的名称

------ 本文结束,感谢您的阅读 ------
本文作者: 程序员青阳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。