Vue Router 4 使用总结

11 阅读6分钟

定义

客户端路由的作用是在单页面应用中将浏览器的url和用户看的内容关联起来,当切换页面时,url会同步修改,但不用从服务器重新加载。 vueRouter基于vue组件建立,可以通过配置路由,告诉vueRouter不同的url展示的组件。

创建路由器实例

  • createRouter()
import {createRouter, createWebHistory} from 'vue-router'
const routes = [    {path: '/', component: Home},    {path: '/about', component: About}]
const router = createRouter({
    history: createWebHistory()
    routes,
})
// 创建路由实例后,需要当作插件注册到app中
create(App).use(router).mount('#app);
  • use()需要在mount之前使用。use的作用:
    • 全局注册RouterView, RouterLink
    • 全局添加router,router, route
    • 启用useRouter, useRoute函数
    • 触发路由器解析初始路由
  • route: 当前路由
  • router: 路由实例,包含所有路由。

动态路由

  • 动态参数使用:表示, 需要使用watch监听动态参数的变化,因为页面会被复用,复用时不会再次调用onMounted方法。
const routes = [{
    path: '/user/:userId',
    component: UserComponents
}]
  • 在路径中使用正则
// 传数字优先匹配第一个路由,顺序不重要。
const route = [
    { path: '/:userId(\\d+)', },  // userId为数字
    { path: '/:userName',} // userName为其他
]
  • 重复的参数,参数名后加+表示一个或多个,加*表示0个或多个,这样的参数获取和传递时为数组
const route = [
    {path: '/:name+'}, // 匹配/one/two/three
    {path: '/:name*'}, // 匹配/,/one,/one/two
    {path: '/:name(\\d+)+'}  // 与正则合用
]
  • sensitivestrict
    • /user 可以匹配/user/,/User路由
    • 设置sensitive为true,大小写敏感,则不能匹配/User
    • 设置strict为true,严格模式,则不能匹配/user/
const route = [
    {path: '/user', sensitive: true, strict: true}
]
  • ?表示可选参数
const route = [
    { path: '/user/:id?' }
]

路由的嵌套使用

  • 可以在组件中嵌套,该组件所在路由的子路由组件将展示在这里。
const routes = [{
   path: '/user/:id',
   children: [
       {path: 'profile', component: ProfileComponent},
       {path: 'post', component: PostComponent}
   ],
   },
]
  • /开始表示根路径,因此子组件的path不需要添加/,url不必嵌套
  • 如果想访问/user/1路由,则需要设置一个path为空的子路由
const routes = [{
    path: '/user/:id',
    children: [
        {path: '', component: UserComponent}
    ]
}]

命名路由

  • 可以给路由设置name属性,跳转时直接根据name跳转,不依据url
const routes = [{    path: '/user/:id',    name: 'user',    comoponent: User,    children: [{name: 'profile', path: 'profile', component: Profile}]
}]

<router-link :to={name: "user", params: {id: "lily"}></router-link>

编程式路由

  • 使用router.push(),参数可以是字符串,或者对象
  • 使用对象时,path和params不能同时出现
router.push('/user/lily'); // url
router.push({
    name: 'user',
    params: {id: 'lily'}
});
router.push({
    path: '/user/lily',
    query: {age: 12}
})
router.push({
    path: '/user',
    params: {id: 'lily'}
}) // 此时会跳转到/user
  • router.replace(),替换当前路由,不会在history中添加新的记录。
  • 用法同push,也可在push中增加一个参数:replace: true
router.replace('/user/lily');
router.push({name: 'user', params: {id: 'lily'}, replace: true})

横跨历史

  • router.go,前进多少,后退多少
router.go(1); // 向前移动一条记录,同router.forward()
router.go(-1); // 向后移动一条记录,同router.back()
router.go(100); // 静默失败
router.go(-100); // 静默失败

命名视图

  • 一个组件中有多个router-view时,对视图进行命名
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})
  • 嵌套的命名视图
/settings/emails                                       /settings/profile
+-----------------------------------+                  +------------------------------+
| UserSettings                      |                  | UserSettings                 |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | |  +------------>  | | Nav | UserProfile        | |
| |     +-------------------------+ |                  | |     +--------------------+ |
| |     |                         | |                  | |     | UserProfilePreview | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+

// userSetting.vue
<div>
    <nav></nav>
    <router-view></router-view>
    <router-view name="helper"></router-view>

</div>

// router
const routes = [{
    path: '/settings',
    component: UserSettings
    children: [
        {path: 'emails', component: UserEmailsSubscriptions},
        {path: 'profile', component: {default: UserProfile, helper: UserProfilePreview }},
    ]
}]

重定向

  • redirect重定向到其他页面
const routes = [
    {path: '/home', redirect: '/'}, // 使用url
    {path: '/home', redirect: {name: 'homePage'}}, // 使用name
    // 使用方法 /search/name => /search?q=name
    {path: '/search/name', redirect: to => { return {path: '/search', query: {q: to.params.name}}}} 
]
  • 重定向不会触发/home的路由守卫

别名

  • alias,可以通过设置别名,让两个地址指向同一个页面
const routes = [
    {path: '/', component: Home, alias: '/home'},
    {path: '/user/:id', component: UserLayout children: [
        // /user/12
        // /user/12/profile
        // /12 3个url指向同一个页面
        {path: 'profile', component: Profile, alias: ['', '/:id']  } 
    ]}
]

将路由参数转为props

  • 可以设置props参数将路由参数转为组件的props
const routes = [
    {path: '/user/:id', component: User, props: true},  // 直接将id作为props传递给组件,组件需在props中定义id。
    {path: '/user/:id', component: {default: User, sidebar: Sidebar}, props: {default: true, sidebar: true}}, // 多个视图的需要分别给每个组件设置。
    {path: '/user', component: User, props: {name: 'lily'}}, // 将props原样传给组件的props
    {path: '/user', component: User, props: to => {return {q: to.query.name}}}, // {q: ''}传给props
]

路由守卫

  • 全局前置守卫router.beforeEach((to, from) => {return ...})
    • 返回false,取消导航
    • 返回undefinedtrue则导航有效,并调用下一个守卫。
    • 返回路由地址,相当于调用router.push(),路由格式同router.push
    • 如果有意外,会抛出error并取消导航。
  • 全局解析守卫router.beforeResolve((to, from) => {}),在导航被确认之前,所有组件内守卫和异步路由组件被解析之后调用。
    • 在此处是获取数据或执行其他操作的理想位置(如用户无法进入页面时需要取消的操作)。
  • 全局后置守卫router.afterEach((to, from) => {}),该守卫无法改变导航,用于分析,更改页面标题,声明页面等辅助功能。
  • 在守卫内的全局注入
const router = createRouter(...);
router.privide('info', 'hhhh');
router.beforeEach(to => {
    const info = inject('info')
})
  • 路由独享的守卫
const routes = [{
    path: '/user',
    component: User,
    beforeEach: (to, from) => {...}
}]

此时的守卫只会在进入路由时触发,params,query,hash改变的时候都不会触发。

  • 组件内守卫
    • 可用的组件内守卫
      • onBeforeRouteEnter:不能调用this,此时this未被创建。可以在next中访问实例。
      <script setup>
      beforeRouteEnter(to, from, next) {
          next(vm => {
              // 通过vm访问实例
          })
      }
      </script>
      
      • onBeforeRouteUpdate:组件复用时调用,例如:/user/1 => /user/2。没有next参数,this可以直接调用。
      • onBeforeRouteLeave:导航离开组件时调用,没有next参数。return false;取消离开。
  • 导航守卫流程
    1. 导航被触发。
    2. 在失活的组件里调用onRouteLeave
    3. 调用全局的beforeEach
    4. 在重用的组件里调用onRouteUpdate
    5. 在路由配置里调用beforeEnter
    6. 解析异步路由
    7. 在被激活的组件中调用onBeforeEnter
    8. 调用全局的beforeResolve
    9. 导航被确认
    10. 调用全局的afterEach
    11. 触发dom更新
    12. 调用onBeforeEnter中传给next的回调。
  • 路由元信息meta,通过meta对象可以添加任何你需要的数据。在导航守卫和路由地址中都可以获取。
const routes = [{
    path: '/user',
    component: User,
    meta: {isAuth: true, name: "用户信息"}
}]

router-view 插槽

  • 给页面切换增加动画效果(transition)或保存状态(keep-alive)
<router-view v-slot={Component, route}>
    <transition :name="route.meta.transition || 'fade'">
        <component :is="Component">
    </transition>
</router-view>

<router-view v-slot={Component}>
    <keep-alive>
        <component :is="Component">
    </keep-alive>
</router-view>

滚动行为

  • 当切换到一个浏览过的页面时,页面自动滚动到上一次的位置,只有在执行history.pushState时才调用(感觉使用场景不多)。
  • 注意: 这个功能只在支持 history.pushState 的浏览器中可用。
const router = createRouter({
    history: createWebHashHistory(),
    routes: [...],
    scrollBehavior: (to, from, savedPosition) => {
        // return 期望滚到的位置
        if (to.hash) {
            return {
                el: to.hash,
                behavior: 'smooth'
            }
        }
    }
})

路由懒加载(动态导入组件)

const User = () => import('...');
const routes = [
    {path: '/user', component: User},
    {path: '/user', component: () => import('...')}
]
  • 如果使用babel,需要添加插件 @babel/plugin-syntax-dynamic-import,此插件已包含在@babel/preset-env 7.8.0及以上版本

类型化路由

  • 使用[unplugin-vue-router]插件,根据约定好的目录结构和命名规则,将指定目录下的vue文件自动生成路由配置,不用手动编写。

其他

  • [vite-plugin-vue-layouts - npm] 该插件通常与vue-router一起使用
  • 一个常见的需求是,同模块的若干页面需要使用同一种布局,比如俱乐部相关的页面的顶部需要展示俱乐部的名称,其他页面顶部需要展示网站名称。 通常实现的方法是,将俱乐部的名称和网站名称定义成公共组件,在每个页面都书写置顶的布局,并引入响应的公共组件。 显然这是非常低效的,通过全局布局 vite-plugin-vue-layouts 可以帮你解决这个麻烦!