Vue3---(7)Router

8 阅读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>