Vue3——Router

Vue Router

路由匹配语法
  1. 在参数中自定义正则

    • 根据参数内容区分两个会匹配完全相同URL的路由

      const routes = [
        // /:orderId -> 仅匹配数字
        { path: '/:orderId(\\d+)' },
        // /:productName -> 匹配其他任何内容
        { path: '/:productName' },
      ]
      
      //  /25 将匹配 /:orderId,其他情况将会匹配 /:productName
      
  2. 可重复的参数

    • *(0个或多个)、+(1个或多个)

      const routes = [
        // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
        { path: '/:chapters+' },
        // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
        { path: '/:chapters*' },
      ]
      
  3. Sensitive 与 strict 路由配置

    • 默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由

      const router = createRouter({
        history: createWebHistory(),
        routes: [
          // 将匹配 /users/posva 而非:
          // - /users/posva/ 当 strict: true
          // - /Users/posva 当 sensitive: true
          { path: '/users/:id', sensitive: true },
          // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
          { path: '/users/:id?' },
        ],
        strict: true, // applies to all routes
      })
      
  4. 可选参数

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

    • 注意

      • * 在技术上也标志着一个参数是可选的,但 ? 参数不能重复

        const routes = [
          // 匹配 /users 和 /users/posva
          { path: '/users/:userId?' },
          // 匹配 /users 和 /users/42
          { path: '/users/:userId(\\d+)?' },
        ]
        
重定向和别名
  1. 重定向的目标

    • /

      const routes = [{ path: '/home', redirect: '/' }]
      
    • 命名路由

      const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
      
    • 动态返回重定向目标

      const routes = [
        {
          // /search/screens -> /search?q=screens
          path: '/search/:searchText',
          redirect: to => {
            // 方法接收目标路由作为参数
            // return 重定向的字符串路径/路径对象
            return { path: '/search', query: { q: to.params.searchText } }
          },
        },
        {
          path: '/search',
          // ...
        },
      ]
      
  2. 相对重定向(重定向到相对位置)

    const routes = [
      {
        // 将总是把/users/123/posts重定向到/users/123/profile。
        path: '/users/:id/posts',
        redirect: to => {
          // 该函数接收目标路由作为参数
          return to.path.replace(/posts$/, 'profile')
        },
      },
    ]
    
  3. 别名

    • 自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制

      const routes = [
        {
          path: '/users',
          component: UsersLayout,
          children: [
            // 为这 3 个 URL 呈现 UserList
            // - /users
            // - /users/list
            // - /people
            { path: '', component: UserList, alias: ['/people', 'list'] },
          ],
        },
      ]
      
    • 如果路由有参数

      const routes = [
        {
          path: '/users/:id',
          component: UsersByIdLayout,
          children: [
            // 为这 3 个 URL 呈现 UserDetails
            // - /users/24
            // - /users/24/profile
            // - /24
            { path: 'profile', component: UserDetails, alias: ['/:id', ''] },
          ],
        },
      ]
      
导航守卫
  1. 全局前置路由

    • false: 取消当前的导航。如果浏览器的 URL 改变了,那么 URL 地址会重置到 from 路由对应的地址。

    • 一个路由地址: 通过一个路由地址重定向到一个不同的地址

      router.beforeEach(async (to, from) => {
         if (
           // 检查用户是否已登录
           !isAuthenticated &&
           // ❗️ 避免无限重定向
           to.name !== 'Login'
         ) {
           // 将用户重定向到登录页面
           return { name: 'Login' }
         }
       })
      
    • 可选的第三个参数next

      router.beforeEach((to, from, next) => {
        if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
        else next()
      })
      
  2. 全局解析路由

    • 解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用

    • 例子:根据路由在元信息属性确保用户访问摄像头的权限

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

    • 对于分析、更改页面标题、声明页面等辅助功能很有用

      router.afterEach((to, from, failure) => {
        if (!failure) sendToAnalytics(to.fullPath)
      })
      
  4. 路由元信息

    • 接收属性对象的meta属性可以在路由地址和导航守卫上都被访问到

      const routes = [
        {
          path: '/posts',
          component: PostsLayout,
          children: [
            {
              path: 'new',
              component: PostsNew,
              // 只有经过身份验证的用户才能创建帖子
              meta: { requiresAuth: true },
            },
            {
              path: ':id',
              component: PostsDetail
              // 任何人都可以阅读文章
              meta: { requiresAuth: false },
            }
          ]
        }
      ]
      
      router.beforeEach((to, from) => {
        // 而不是去检查每条路由记录
        // to.matched.some(record => record.meta.requiresAuth)
        if (to.meta.requiresAuth && !auth.isLoggedIn()) {
          // 此路由需要授权,请检查是否已登录
          // 如果没有,则重定向到登录页面
          return {
            path: '/login',
            // 保存我们所在的位置,以便以后再来
            query: { redirect: to.fullPath },
          }
        }
      })
      
  5. 数据获取

    • 导航完成后获取数据

      <template>
        <div class="post">
          <div v-if="loading" class="loading">Loading...</div>
      
          <div v-if="error" class="error">{{ error }}</div>
      
          <div v-if="post" class="content">
            <h2>{{ post.title }}</h2>
            <p>{{ post.body }}</p>
          </div>
        </div>
      </template>
      
      <script setup>
      import { ref, watch } from 'vue'
      import { useRoute } from 'vue-router'
      import { getPost } from './api.js'
      
      const route = useRoute()
      
      const loading = ref(false)
      const post = ref(null)
      const error = ref(null)
      
      // 侦听路由的参数,以便再次获取数据
      watch(() => route.params.id, fetchData, { immediate: true })
      
      async function fetchData(id) {
        error.value = post.value = null
        loading.value = true
        
        try {
          // 用获取数据的工具函数 / API 包裹器替换 `getPost`
          post.value = await getPost(id)  
        } catch (err) {
          error.value = err.toString()
        } finally {
          loading.value = false
        }
      }
      </script>
      
    • 导航前获取数据

      export default {
        data() {
          return {
            post: null,
            error: null,
          }
        },
        beforeRouteEnter(to, from, next) {
          try {
            const post = await getPost(to.params.id)
            // `setPost` 方法定义在下面的代码中
            next(vm => vm.setPost(post))
          } catch (err) {
            // `setError` 方法定义在下面的代码中
            next(vm => vm.setError(err))
          }
        },
        // 路由改变前,组件就已经渲染完了
        // 逻辑稍稍不同
        async beforeRouteUpdate(to, from) {
          this.post = null
          getPost(to.params.id).then(this.setPost).catch(this.setError)
        },
        methods: {
          setPost(post) {
            this.post = post
          },
          setError(err) {
            this.error = err.toString()
          }
        }
      }
      
  6. RouterView插槽

    • KeepAlive & Transition

      • 当在处理 KeepAlive 组件时,通常想要保持路由组件活跃,而不是 RouterView 本身,可以将 KeepAlive 组件放置在插槽内

      • 可以在 Transition 组件内使用 KeepAlive 组件

        <router-view v-slot="{ Component }">
          <transition>
            <keep-alive>
              <component :is="Component" />
            </keep-alive>
          </transition>
        </router-view>
        
    • 模板引用

      • 使用插槽可以让我们直接将模板引用放置在路由组件上

        <router-view v-slot="{ Component }">
          <component :is="Component" ref="mainContent" />
        </router-view>
        
  7. 滚动行为

    • 自定义路由切换时页面如何滚动

    • 注意:这个功能只在支持 history.pushState 的浏览器中可用(history.pushState是 HTML5 History API 的一个方法,允许开发者通过 JavaScript 动态修改浏览器的地址栏 URL,而无需重新加载页面)

    • 传递一个 CSS 选择器或一个 DOM 元素

      const router = createRouter({
        scrollBehavior(to, from, savedPosition) {
          // 始终在元素 #main 上方滚动 10px
          return {
            // 也可以这么写
            // el: document.getElementById('main'),
            el: '#main',
            // 在元素上 10 像素
            top: 10,
          }
        },
      })
      
    • 返回一个 falsy 的值,或者是一个空对象,那么不会发生滚动

    • 返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样

      const router = createRouter({
        scrollBehavior(to, from, savedPosition) {
          if (savedPosition) {
            return savedPosition
          } else {
            return { top: 0 }
          }
        },
      })
      
    • 模拟 “滚动到锚点” 的行为

      const router = createRouter({
        scrollBehavior(to, from, savedPosition) {
          if (to.hash) {
            return {
              el: to.hash,
            }
          }
        },
      })
      
    • 延迟滚动

      const router = createRouter({
        scrollBehavior(to, from, savedPosition) {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({ left: 0, top: 0 })
            }, 500)
          })
        },
      })
      
  8. 路由懒加载

    • 动态导入

      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 API

TS接口

Router
  1. 属性

    • currentRoute(只读)

      • fullPath:包括 searchhash 在内的完整地址
      • hash:当前地址的 hash。如果存在则以 # 开头
      • matched:数组,只包含直接的组件,不包含重定向的记录
      • meta:从所有匹配的路由记录中合并的 meta 属性
      • name:匹配的路由名称
      • params:从 path 中提取出来并解码后的参数对象
      • path:经过百分号编码的 URL 中的 pathname 段
      • query:代表当前地址的 search 属性的对象
      • rediredtedForm:包含在重定向到当前地址之前,我们最初想访问的地址
    • listening:boolean

      • 允许关闭历史事件的监听器
    • options(只读)

      • 创建路由器时的原始选项对象

      • end:其 RegExp 是否应该在末尾加一个 $ 以匹配到末尾

      • history:路由器使用的历史记录模式

      • linkActiveClass:匹配当前路由的 RouterLink 默认的 CSS class

      • linkExactActiveClass:严格匹配当前路由的 RouterLink 默认的 CSS class

      • rotues(只读):应该添加到路由器的初始路由列表

      • scrollBehavior:当在页面之间导航时控制滚动的功能。可以返回一个 Promise 来延迟滚动

        function scrollBehavior(to, from, savedPosition) {
          // `to` 和 `from` 都是路由路径
          // `savedPosition` 如果不存在可以为 null
        }
        
      • sensitive:使该 RegExp 区分大小写

      • strict:是否禁止尾部斜线

      • stringifyQuery:对查询对象进行字符串化的自定义实现

      • parseQuery:解析查询的自定义实现

  2. 方法

    • addRoute(parentName, route): () => void

      • 添加一个新的路由记录,将其作为一个已有路由的子路由
      • parentName:route 应该被加入到的父级路由记录
      • route:要加入的路由记录
    • afterEach(guard): () => void

      • 添加一个导航钩子,它会在每次导航之后被执行。返回一个用来移除该钩子的函数

      • guard:要加入的导航钩子

        router.afterEach((to, from, failure) => {
          if (isNavigationFailure(failure)) {
            console.log('failed navigation', failure)
          }
        })
        
    • back(): void

    • 通过调用 history.back() 在可能的情况下在历史中后退。相当于 router.go(-1)

    • beforeEach(guard): () => void

    • 添加一个导航钩子,它会在每次导航之前被执行。返回一个用来移除该钩子的函数。

    • guard:要加入的导航钩子

    • beforeResolve(guard): () => void

    • 添加一个导航守卫,它会在导航将要被解析之前被执行。此时所有组件都已经获取完毕,且其它导航守卫也都已经完成调用。返回一个用来移除该守卫的函数

      router.beforeResolve(to => {
        if (to.meta.requiresAuth && !isAuthenticated) return false
      })
      
    • forward(): void

      • 通过调用 history.forward() 在可能的情况下在历史中前进。相当于 router.go(1)
    • getRoutes():[]

    • 获得所有路由记录的完整列表

    • go(delta): void

      • 允许你在历史中前进或后退。相当于 router.go()
    • hasRoute(name): boolean

      • 检查一个给定名称的路由是否存在
    • isReady(): Promise<void>

    • 返回一个 Promise,它会在路由器完成初始导航之后被解析,也就是说这时所有和初始路由有关联的异步入口钩子和异步组件都已经被解析。如果初始导航已经发生,则该 Promise 会被立刻解析。

    • 这在服务端渲染中确认服务端和客户端输出一致的时候非常有用。注意在服务端需要手动加入初始地址,而在客户端,路由器会从 URL 中自动获取。

    • onError(handler): () => void

    • 添加一个错误处理器,它会在每次导航遇到未被捕获的错误出现时被调用。其中包括同步和异步被抛出的错误、在任何导航守卫中返回或传入 next 的错误、尝试解析一个需要渲染路由的异步组件时发生的错误。

    • handler:要注册的错误处理器

    • push(to): Promise<undefined | void | NavigationFailure>

      • 程序式地通过将一条记录加入到历史栈中来导航到一个新的 URL
      • to:要导航到的路由
    • removeRoute(name): void

      • 根据其名称移除一个现有的路由
    • replace(to): Promise<undefined | void | NavigationFailure>

      • 程序式地通过替换历史栈中的当前记录来导航到一个新的 URL
    • resolve(to, currentLocation?): RouteLocation& { href: string }

      • 返回一个路由地址的规范化版本。同时包含一个包含任何现有 base 的 href 属性
    • to:要解析的原始路由地址

    • currentLocation?:可选的被解析的当前地址

变量

  1. RouterLink

    • 用来渲染一个链接的组件,该链接在被点击时会触发导航
  2. RouterView

    • 用于显示用户当前所处路由的组件
  3. START_LOCATION

    • 路由器的初始路由位置。可以在导航守卫中使用来区分初始导航

      import { START_LOCATION } from 'vue-router'
      
      router.beforeEach((to, from) => {
        if (from === START_LOCATION) {
          // 初始导航
        }
      })
      

函数

  1. createMemoryHistory(base?): RouterHistory
  • 创建一个基于内存的历史。该历史的主要目的是为了处理服务端渲染。它从一个不存在的特殊位置开始
  • 用户可以通过调用 router.pushrouter.replace 将该位置替换成起始位置
  1. createRouter(options): Router

    • 创建一个可以被 Vue 应用使用的 Router 实例
  2. createWebHashHistory(base?): RouterHistory

    • 创建一个 hash 模式的历史
  3. createWebHistory(base?): RouterHistory

    • 创建一个 HTML5 历史
  4. isNavigationFailure(error, type?): error is NavigationRedirectError

    • 检查一个对象是否是 NavigationFailure

      import { isNavigationFailure, NavigationFailureType } from 'vue-router'
      
      router.afterEach((to, from, failure) => {
        // 任何类型的导航失败
        if (isNavigationFailure(failure)) {
          // ...
        }
        // 重复的导航
        if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
          // ...
        }
        // 中止或取消的导航
        if (isNavigationFailure(failure, NavigationFailureType.aborted | NavigationFailureType.canceled)) {
          // ...
        }
      })
      
  5. loadRouteLocation(route): Promise

    • 确保路由被加载,所以它可以作为一个 prop 传递给 <RouterView>
  6. onBeforeRouteLeave(leaveGuard): void

  • 添加一个导航守卫,不论当前位置的组件何时离开都会触发。类似于 beforeRouteLeave,但可以在任意组件中使用。当组件被卸载时,该守卫会被移除
  1. onBeforeRouteUpdate(updateGuard): void

    • 添加一个导航守卫,不论当前位置何时被更新都会触发。类似于 beforeRouteUpdate,但可以在任何组件中使用。当组件被卸载时,该守卫会被移除
  2. useLink(props): Object

  3. useRoute(): RouteLocationNormalizedLoaded

- 返回当前的路由地址
  1. useRouter(): Router
- 返回路由器实例
### 如何在 Vue 3 中监听路由变化 为了实现在 Vue 3 应用程序中监听路由的变化,可以利用组合式 API 的 `watch` 函数来监视由 `vue-router` 提供的当前激活的路由对象。每当导航发生时——无论是通过编程方式还是用户交互触发——都会调用相应的回调函数。 下面是一段 TypeScript 编写的代码片段展示了如何设置这样的观察器: ```typescript <script lang="ts"> import { defineComponent, watch } from "vue" import { useRoute } from "vue-router" export default defineComponent({ setup() { const route = useRoute() watch( () => route.fullPath, (newPath, oldPath) => { console.log(`Navigated from ${oldPath} to ${newPath}`) // 可在此处执行其他逻辑操作,比如更新页面组件的状态或加载数据等。 }, { immediate: true } ) return {} } }) </script> ``` 此代码定义了一个新的 Vue 组件,在其 `setup()` 函数内部使用了来自 `vue-router` 的 `useRoute` 钩子获取到当前的路由实例,并对其路径进行了监控[^1]。当路径发生变化时,会打印出新旧路径的信息并允许开发者在这里加入额外的操作逻辑。 对于更复杂的场景,如果想要监听整个路由对象而不是仅仅关注于 URL 路径部分,则可以直接传递 `route` 到 `watch` 函数的第一个参数位置上,这样就可以访问更多关于匹配记录、查询参数等方面的数据了。 此外,还可以配置选项 `{ flush: &#39;pre&#39; | &#39;post&#39; | &#39;sync&#39;, immediate: boolean, deep: boolean }` 来调整何时以及怎样触发侦听器的行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值