Vue3---(7)Router

14 阅读13分钟

目录

安装 

基本使用

创建路由器实例

 注册路由器插件

动态路由匹配

嵌套路由

命名路由

编程式导航

路由组件传参(props)

重定向

路由元信息

导航守卫

全局前置守卫

 全局解析守卫

全局后置钩子

 组件内的守卫

路由模式

Hash模式

History模式(HTML5模式)

 Memory模式

面试重点

导航守卫中next()的三种用法?

Vue3中路由API的变化?

Vue Router两种模式的区别?

 如何实现路由权限控制?

性能优化方案?

安装 

  • 现有项目通过npm 安转(vue3需要安装4.X)npm install vue-router@4
npm install vue-router@4

  • 创建Vue3工程时可以直接选择添加router

基本使用

创建路由器实例

 src/router/index.ts 文件中开始创建路由器实例

import { createMemoryHistory, createRouter } from 'vue-router'

import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'

const routes = [
  { path: '/', component: HomeView },
  { path: '/about', component: AboutView },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})

 注册路由器插件

main.ts中开始注册路由器插件

路由器插件主要作用

  1. 全局注册 RouterView和RouterLink 组件
  2. 启用useRouter() 和useRoute()组合式函数
  3. 触发路由器解析初始路由 
createApp(App)
  .use(router)
  .mount('#app')

等价于

const app = createApp(App)
app.use(router)
app.mount('#app')

动态路由匹配

路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 route.params 的形式暴露出来

import User from './User.vue'

// 这些都会传递给 `createRouter`
const routes = [
  // 动态字段以冒号开始
  { path: '/users/:id', component: User },
]

同一个路由可以设置多个路径参数,它会映射到route.params上的对应字段

匹配模式匹配路径route.params
/users/:username/users/eduardo{ username: 'eduardo' }
/users/:username/posts/:postId/users/eduardo/posts/123{ username: 'eduardo', postId: '123' }

可重复参数

需要匹配具有多个部分的路由,如 /first/second/third,你应该用 *(0 个或多个)和 +(1 个或多个)将参数标记为可重复:

const routes = [
  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  { path: '/:chapters+' },
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  { path: '/:chapters*' },
]

这将为你提供一个参数数组,而不是一个字符串,并且在使用命名路由时也需要你传递一个数组

// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 产生 /
router.resolve({ name: 'chapters', params: { chapters: ['a', 'b'] } }).href
// 产生 /a/b

// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 抛出错误,因为 `chapters` 为空

可选参数

可以通过使用 ? 修饰符(0 个或 1 个)将一个参数标记为可选:

const routes = [
  // 匹配 /users 和 /users/posva
  { path: '/users/:userId?' },
  // 匹配 /users 和 /users/42
  { path: '/users/:userId(\d+)?' },
]

嵌套路由

一个被渲染的组件也可以包含自己嵌套的 <router-view>,要将组件渲染到这个嵌套的 router-view 中,我们需要在路由中配置 children

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

命名路由

相当于给路由取一个别名

const routes = [
  {
    path: '/user/:username',
    name: 'profile', 
    component: User
  }
]

 使用 name 而不是 path 来传递 to 属性给 <router-link>

<router-link :to="{ name: 'profile', params: { username: 'erina' } }">
  User profile
</router-link>

注意: 所有路由的命名必须是唯一的,如果有相同的命名,路由器只会保留最后一条 

编程式导航

 路由组件的两个重要的属性:$route$router变成了两个hooks

import {useRoute,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)

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

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

声明式编程式
<router-link :to="...">router.push(...)
// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

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

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

注意1:传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path。使用path时要手写完整的带有参数的 path

注意2:传递params参数时,需要提前在规则中占位。

路由组件传参(props)

三种传参方式

{
	name:'xiang',
	path:'detail/:id/:title/:content',
	component:Detail,

  // props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
  // props:{a:1,b:2,c:3}, 

  // props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
  // props:true
  
  // props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
  props(route){
    return route.query
  }
}

重定向

 将特定的路径,重新定向到已有路由。

{
    path:'/',
    redirect:'/about'
}

路由元信息

meta属性可以用来实现谁可以访问路由,以及一切任意信息附加到路由上,并且它可以在路由地址和导航守卫上都被访问到 

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

 可以通过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 },
    }
  }
})

导航守卫

全局前置守卫

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

接受两个参数--to,from

  • to: 即将要进入的目标
  • from: 当前导航正要离开的路由
const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

可以返回的值

false

 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

一个路由地址

 通过一个路由地址重定向到一个不同的地址,如同调用 router.push(),且可以传入诸如 replace: true 或 name: 'home' 之类的选项。它会中断当前的导航,同时用相同的 from 创建一个新导航。

next

之前的router版本中可以使用,但是新版本已经删除,不过仍然支持,不过使用的时候,确保 next 在任何给定的导航守卫中都被严格调用一次。

// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

 全局解析守卫

router.beforeResolve 注册一个全局守卫。它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

全局后置钩子

不会接受next函数,也不会改变导航,更多的是 分析、更改页面标题、声明页面等辅助功能

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

 组件内的守卫

可以为路由组件添加以下配置:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
<script>
export default {
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}
</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 的回调函数,创建好的组件实例会作为回调函数的参数传入。

路由模式

Hash模式

以#分割实际的URL,#以后的部分不会被当做实际的URL,所以不需要在服务器端进行特殊处理,但是它影响SEO优化

import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    //...
  ],
})

History模式(HTML5模式)

 History模式会使URL看起来比较符合正常的地址路径,但是对于SPA应用来说,需要在服务器端进行配置

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    //...
  ],
})

 Memory模式

不会与 URL 交互也不会自动触发初始导航。这使得它非常适合 Node 环境和 SSR。它是用 createMemoryHistory() 创建的,并且需要你在调用 app.use(router) 之后手动 push 到初始导航。它不会有历史记录,所以也就无法前进或者后退

import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
  history: createMemoryHistory(),
  routes: [
    //...
  ],
})

面试重点

导航守卫中next()的三种用法?

next() // 正常放行
next(false) // 中断当前导航
next('/login') // 重定向到新路径

Vue3中路由API的变化?

  • 使用createRouter替代new VueRouter
  • history配置项独立(createWebHistory,createWebHashHistory)
  • $route$router变成了两个hooks(useRouter和useRoute)

Vue Router两种模式的区别?

  • ​hash模式​​:使用URL hash值(#后的部分),兼容性好,无需服务器配置
  • ​history模式​​:依赖HTML5 History API,需要服务器配置支持
  • ​memory模式​​:适用于非浏览器环境(如Electron)

 如何实现路由权限控制?

// 方案一:全局守卫 + 路由元信息
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !store.state.auth) {
    next('/login')
  } else {
    next()
  }
})

// 方案二:动态路由 + 用户权限验证
function setupRoutes(userRole) {
  const routes = filterRoutesByRole(userRole)
  routes.forEach(route => router.addRoute(route))
}

性能优化方案?

路由懒加载

Vue Router 支持动态导入,这意味着你可以用动态导入代替静态导入,

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

const router = createRouter({
  // ...
  routes: [
    { path: '/users/:id', component: UserDetails }
    // 或在路由定义里直接使用它
    { path: '/users/:id', component: () => import('./views/UserDetails.vue') },
  ],
})

把组件按组分块

webpack实现方案

const UserDetails = () =>
  import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
  import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
  import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')

Vite实现方案

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      // https://rollupjs.org/guide/en/#outputmanualchunks
      output: {
        manualChunks: {
          'group-user': [
            './src/UserDetails',
            './src/UserDashboard',
            './src/UserProfileEdit',
          ],
        },
      },
    },
  },
})

预加载关键路由

// 在用户登录后预加载管理界面
router.beforeEach((to, from, next) => {
  if (to.name === 'Dashboard') {
    import('./views/AdminPanel.vue')
  }
  next()
})

Keep-Alive缓存

<router-view v-slot="{ Component }">
  <keep-alive :include="['Home', 'User']">
    <component :is="Component" />
  </keep-alive>
</router-view>