vue-router

269 阅读7分钟

vue-router相关知识

动态路由匹配

一个参数:

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})
const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

多个参数:

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

路由对象属性

  1. $route.path(string) 等于当前路由路径(url)
  2. $route.params(object) 对象包含了动态参数的key/value
  3. $route.query(object) 对象表示url的查询参数,例如路径/foo?user=1,则有router.query.user==1,如果没有查询参数,则是个空对象。
  4. $route.hash(string) 当前路由的hash值(带#),如果没有hash值,则为空字符串
  5. $route.fullPath(string) 完成解析后的URL,包含查询参数和hash的完整路径
  6. $route.matched(string) 一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。
const router = new VueRouter({
 routes: [
   // 下面的对象就是路由记录
   {
     path: '/foo',
     component: Foo,
     children: [
       // 这也是个路由记录
       { path: 'bar', component: Bar }
     ]
   }
 ]
})
  1. $route.name(string) 等于当前路由的名称
  2. $route.redirectedFrom 如果存在重定向,即为重定向来源的路由的名字

路由参数变化,组件被复用

当使用路由参数时,原来的组件实例会被复用,不会重新创建和销毁,这就意味着组件的生命周期钩子不会再被调用 复用组件时,想对路由参数的变化作出响应的话,可在watch里检测$route对象:

  watch: {
    $route(to, from) {
      // 对路由变化作出响应...
    }

或者组件内调用beforeRouteUpdate钩子

  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },

beforeRouteUpdate具体用法:

 export default{
  data(){
    return {}
  },
  
 beforeRouteUpdate(to,from,next){
     console.log(to,from,next)
      if(to.fullPath!=from.fullPath){
       next()
       this.changeUser()
      }
  },
  
  methods:{
      changeUser(){
        getUserBaseInfo({userId:this.$route.params.id}).then(res=>{
          if(res.success){
            this.stuInfo = res.data;
          }else{
           this.$message.error(res.message)
          }
     })
    }
  }
}

子路由的path值

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `
}
const router = new VueRouter({
  routes: [
    {
      path: '/user/:id',
      component: User,
      children: [
        {
          ///user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User<router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          ///user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User<router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

若想要被复用的组件被重新创建时(重新触发创建销毁钩子函数),可以给router-view添加key

 <router-view :key="$route.params.id"></router-view>

编程式导航router.push()

等同于:声明式导航:<router-link :to="...">

// 字符串
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、router.push里同时传path和params时,params会被忽略,而query不存在这种情况。

// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

2、router.push里同时传路由的name和params时生效,或者手写完整的带参数的path

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123

3、如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。

  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },

beforeRouteUpdate具体用法:

 export default{
  data(){
    return {}
  },
  
 beforeRouteUpdate(to,from,next){
     console.log(to,from,next)
      if(to.fullPath!=from.fullPath){
       next()
       this.changeUser()
      }
  },
  
  methods:{
      changeUser(){
        getUserBaseInfo({userId:this.$route.params.id}).then(res=>{
          if(res.success){
            this.stuInfo = res.data;
          }else{
           this.$message.error(res.message)
          }
     })
    }
  }
}

路由重定向(重定向目标为静态或动态)

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

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

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

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

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

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

使用 props 将组件和路由解耦:

当路由中的props属性值为true时,route.params 将会被设置为组件属性。

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

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

其中template中的{{id}}相当于:{{ $route.params.id}}

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}

hash模式和history模式

  1. hash模式的#url从未发送到服务器,因此服务器层面上不需要做任何配置;

  2. history模式,当使用这种历史模式时,URL 会看起来很 "正常",例 如 https://example.com/user/id。漂亮!

不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。这就尴尬了。

不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧! 服务器该如何设置,具体见vue-router官方文档

导航守卫

路由参数或查询的改变不会触发进入/离开导航守卫

全局前置守卫

router.beforeEach 注册一个全局前置守卫:

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

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

router.beforeEach的返回值有两种情况:

  • false:取消当前导航,将url地址重置到from路由对应的地址
  • 一个路由地址:内容等同于router.push()中的选项
 router.beforeEach(async (to, from) => {
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
     // 将用户重定向到登录页面
     return { name: 'Login' }
   }
 })

第三个参数next:Function

  • next():进入管道中的下一个钩子
  • next(false):中断当前的导航,url地址重置到from路由对应的地址
  • next('/')或者next({path: '/'}):跳转到一个不同的地址。参数等同于router.push()里的任意选项
  • next(error):如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
router.onError(callback)

全局解析守卫

是在所有组件内守卫和异步路由组件被解析完后才被调用

全局后置钩子

路由独享守卫

即在路由配置上直接定义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`
  }
}
beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}
beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}
beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

image.png

路由元信息(meta)

有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 meta 字段:

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true }
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false }
      }
    ]
  }
]

那么如何访问这个 meta 字段呢?

首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,它可能匹配多个路由记录。

例如,根据上面的路由配置,/posts/new 这个 URL 将会匹配父路由记录 (path: '/posts') 以及子路由记录 (path: 'new')。

一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航守卫中的路由对象)的$route.matched 数组。我们需要遍历这个数组来检查路由记录中的 meta 字段,但是 Vue Router 还为你提供了一个 $route.meta 方法,它是一个非递归合并所有 meta 字段的(从父字段到子字段)的方法。这意味着你可以简单地写

router.beforeEach((to, from) => {
  // 而不是去检查每条路由记录
  // to.matched.some(record => record.meta.requiresAuth)
  if (to.meta.requiresAuth && !auth.isLoggedIn()) {
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
      path: '/login',
      // 保存我们所在的位置,以便以后再来
      query: { redirect: to.fullPath },
    }
  }
})

过渡动效

<router-view v-slot="{ Component }">
  <transition name="fade">
    <component :is="Component" />
  </transition>
</router-view>

单个路由的过渡 如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在<transition> 上:

const routes = [
  {
    path: '/custom-transition',
    component: PanelLeft,
    meta: { transition: 'slide-left' },
  },
  {
    path: '/other-transition',
    component: PanelRight,
    meta: { transition: 'slide-right' },
  },
]

Vue 可能会自动复用看起来相似的组件,从而忽略了任何过渡。幸运的是,可以添加一个 key 属性来强制过渡。这也允许你在相同路由上使用不同的参数触发过渡:

<router-view v-slot="{ Component, route }">
  <transition name="fade">
    <component :is="Component" :key="route.path" />
  </transition>
</router-view>

滚动行为

路由懒加载

// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})

动态路由

对路由的添加通常是通过 routes 选项来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由

添加路由

动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。它们注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由。

router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)

router.replace(router.currentRoute.value.fullPath)

//router.currentRoute相当于$route。但是$route.path或者 $route.fullPath只能在组件中用,js模块中要使用 router.currentRoute

在导航守卫中添加路由

如果你决定在导航守卫内部添加或删除路由,你不应该调用 router.replace(),而是通过返回新的位置来触发重定向:

router.beforeEach(to => {
  if (!hasNecessaryRoute(to)) {
    router.addRoute(generateRoute(to))
    // 触发重定向
    return to.fullPath
  }
})

删除路由

有几个不同的方法来删除现有的路由:

  • 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:

    router.addRoute({ path: '/about', name: 'about', component: About })
    // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
    router.addRoute({ path: '/other', name: 'about', component: Other })
    
  • 通过调用 router.addRoute() 返回的回调:

    const removeRoute = router.addRoute(routeRecord)
    removeRoute() // 删除路由如果存在的话
    

    当路由没有名称时,这很有用。

  • 通过使用 router.removeRoute() 按名称删除路由:

     router.addRoute({ path: '/about', name: 'about', component: About })
     // 删除路由
     router.removeRoute('about')
    

添加嵌套路由

要将嵌套路由添加到现有的路由中,可以将路由的 name 作为第一个参数传递给 router.addRoute(),这将有效地添加路由,就像通过 children 添加的一样:

router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })

这等效于:

router.addRoute({
  name: 'admin',
  path: '/admin',
  component: Admin,
  children: [{ path: 'settings', component: AdminSettings }],
})

查看现有路由

Vue Router 提供了两个功能来查看现有的路由: