【Vue3 生态】VueRouter 路由核心知识点

1. 动态路由

1.1 动态路由匹配

路由分为静态路由动态路由。上面讲过的类似 ‘/login’ 这样写死的就是静态路由。

动态路由通过在路径中使用一个动态字段(简称:路径参数),来将不同的信息映射到同一个组件中。

如:有一个 User 组件用于渲染所有用户,但这些用户的 ID 是不同的

# User.vue
<script setup>
    import { useRouter } from 'vue-router'
    const router = useRouter()
    console.log(router.params)
</script>
<template>
    <div>
        {
   
   { $route.params.id }}
    </div>
</template>
import { createRouter, createWebHistory } from 'vue-router'

import Login from '@/views/Login.vue'

const routes = [
    {
        path: '/',
        redirect: '/Login'
    },
    {
    	path: '/login',
    	name: 'login',
    	component: Login,
    	meta: {
    		title: '登录',
    		requiresAuth: false // 设置为 false 或 不设置代表不需要校验身份,任何人都可以访问这个页面
    	}
    },
    {
        path: '/user/:id',
        name: 'user',
        component: () => import('@/views/User.vue'),
        meta: {
            title: '用户管理', // 页面标题
            requiresAuth: true  // 登录身份校验成功,才可以访问这个页面
        }
    }
]

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes
})

router.beforeEach((to, from) => {
    if(to.meta.title) document.title = to.meta.title
})

export default router
  1. meta 路由元信息:可用于给路由附加如:过渡名称、访问路由权限、页面标题等任意信息。可以通过路由地址或导航守卫进行访问。
  2. router.beforeEach((to,from) => {}) 注册一个全局前置守卫,它会在每次导航之前被执行。
    • to 即将要进入的目标
    • from 当前导航正要离开的路由
  3. 路径参数用冒号:表示。当一个路由被匹配时,它的 params 值将在每个组件中以 $route.params 的形式暴露出来。

1.2 动态路由另外一种配置方式

可以通过 children 配置动态参数

const routes = [
    // ...其它路由参数
    {
        path: '/user',
        name: 'user',
        children: [
        	{
        		path: ':id',
        		component: () => import('@/views/User.vue'),
        		meta: {
                    title: '用户管理',
                    requiresAuth: true
                },
        	},
        	{
        		path: 'Add',
        		component: () => import('@/views/UserAdd.vue'),
        		meta: {
        			title: '用户新增',
        			requiresAuth: true
        		}
        	}
        ]
    }
]
  1. / 开头的嵌套路径将被视为根路径。这允许你利用组件嵌套,而不必使用嵌套的 URL。

1.3 路由的匹配语法——在参数中自定义正则

两个路由 /:orderId/:productName,两者会匹配完全相同的 URL,所以我们需要一种方法来区分它们。

假设:orderId 总是一个数字,而 productName 可以是任何东西,所以我们可以在括号中为参数指定一个自定义的正则

const routes = [
  // /:orderId -> 仅匹配数字
  { path: '/:orderId(\\d+)' },
  // /:productName -> 匹配其他任何内容
  { path: '/:productName' },
]

这时,跳转到 /25 将匹配 /:orderId,其他情况将会匹配 /:productName

确保转义反斜杠( \ ),就像我们对 \d (变成\\d)所做的那样,在 JavaScript 中实际传递字符串中的反斜杠字符。

更多路由匹配语法详见 Vue Router 官网

1.4 对路由的增删改操作API

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

  1. 添加路由 router.addRoute()
  2. 移除路由 router.removeRoute()
// 添加路由
router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)
router.replace(router.currentRoute.value.fullPath)
// 删除路由
router.removeRoute('about')

如果你需要等待新的路由显示,可以使用 await router.replace()

2. 命名视图

如果想在同一个界面中定义多个同级视图,就需要给 router-view 设置名字(默认 default),这样就可以有多个出口了。

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

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 | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+
  • Nav 只是一个常规组件。
  • UserSettings 是一个视图组件。
  • UserEmailsSubscriptionsUserProfileUserProfilePreview 是嵌套的视图组件。

3 路由组件传参

带路由的项目组件中,使用 $route 会与路由紧密耦合,这限制了组件的灵活性,使得它只能用于特定的 URL。

如果想要解除这种行为,可以通过 props 进行配置

3.1 路由 props 属性值为 true

# 路由配置代码
{
    path: '/user',
    name: 'user',
    component: () => import('@/views/User.vue'),
    children: [
        { 
            path: ':id',
            component: () => import('@/views/UserHome.vue'),
            // 路由配置 props:true,这样映射的组件中可以通过 props 定义对应的路由参数,从而达到$route对象和组件解耦的效果
            props: true
        }
    ]
}
# 路由映射组件代码
<script setup>
    import { defineProps } from 'vue'
    import { useRouter } from 'vue-router'
    const router = useRouter()
	// 组件 props 中定义路由参数对应的属性
    const props = defineProps(['id'])
</script>
<template>
    <div>
    	// 通过 $route 对象调用路由参数
       <em> {
   
   { $route.params.id }} </em>
       <br>
       // 通过 props 中定义的属性调用路由参数
       <strong>{
   
   { props.id }}</strong>
    </div>
</template>
  1. 当路由配置属性 props 设置为 true 时,route.params 将被设置为组件的 props
  2. 路由组件传参的好处是:使组件更容易重用和测试。

如果路由中含有命名视图,那么你必须为每个命名视图定义 props 配置:

const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false }
  }
]

3.2 路由 props 属性值为一个普通对象

props 是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用。

# 路由代码
{
    path: '/user',
    name: 'user',
    component: () => import('@/views/User.vue'),
    children: [
        { 
            path: ':id',
            component: () => import('@/views/UserHome.vue'),
            // props 是一个对象,会将这个对象传递给对应的组件。props 中无法获取路由参数
            props: { num: 1234567890 }
        }
    ]
}
# 映射组件的代码
<script setup>
    import { defineProps } from 'vue'

    const props = defineProps(['id', 'num'])
    console.log(props, 11111111111) //Proxy(Object) {num: 1234567890, id: undefined} 11111111111
</script>
<template>
    <div>
       <em> {
   
   { $route.params.id }} </em>
       <br>
       <strong>{
   
   { props.id }}</strong>  <!-- props 中无法获取路由参数 -->
       <hr>
       <ins>{
   
   { props.num }}</ins>
    </div>
</template>

3.4 路由 props 属性值为一个函数

你可以创建一个返回 props 的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。

const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }
]

URL /search?q=vue 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件。

4. history 历史模式

在创建路由器实例时,history 配置允许我们在不同的历史模式中进行选择。

  • Hash 模式

    hash 模式是用 createWebHashHistory() 创建的,不依赖于服务器配置。但不会被搜索引擎处理,SEO效果较差。

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

    createWebHashHistory(base?) 参数为一个路径

    // 基于 https://example.com/folder 的 hash 模式
    createWebHashHistory() // 给出一个 `https://example.com/folder#` 的 URL
    createWebHashHistory('/folder/') // 给出一个 `https://example.com/folder/#` 的 URL
    
  • HTML5 模式

    HTML5 模式使用 createWebHistory() 创建 ,需要正确的配置服务器。这种方式会被搜索引擎处理,SEO效果较好。是单页应用最常见的模式。

    如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。各服务器配置示例

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

    当使用这种历史模式时,URL 会看起来很 “正常”,例如 https://example.com/user/id

万能路由显示 404 页面:

const router = createRouter({
	history: createHistory(),
	routes: [{ path: '/:pathMatch(.*)', component: NotFoundComponent }]
})

5 路由守卫

Vue Router 提供了多种路由守卫,如:全局路由守卫、组件内路由守卫或单个路由独享守卫。它们主要通过跳转或取消的方式守卫导航。

5.1 全局前置守卫

全局前置守卫通过 router.beforeEach(callback(to, from)) 注册。参数是一个守卫方法,其接收两个参数:

  • to 即将要进入的目标
  • from 当前导航正要离开的路由

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

可以返回的值:

  • false:用于取消当前导航。

    const router = createRouter({ ... })
    
    router.beforeEach((to, from) => {
      // ...
      // 返回 false 以取消导航
      return false
    })
    
  • 一个路由地址:如同调用 router.push() 一样,可以跳转到这个指定的路由地址,可以在返回对象中通过如:replace:true 或 name: ‘home’ 之类的属性来指定这个路由地址。

    router.beforeEach(async (to, from) => {
    	if (
    		// 检查用户是否已登录
    		!isAuthenticated &&
    		// 避免无限重定向
    		to.name != 'Login'
    	) {
    		// 将用户重定向到登录页面
    		return { name: 'Login' }
    	}
    })
    
    router.beforeEach(async (to, from) => {
      // canUserAccess() 返回 `true` 或 `false`
      const canAccess = await canUserAccess(to)
      if (!canAccess) return '/login'
    })
    

5.2 全局解析守卫

全局解析守卫通过 router.beforeResolve 注册。解析守卫刚好会在每次导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。

5.3 全局后置钩子

后置钩子不会改变导航本身。对于分析、更改页面标题、声明页面等辅助功能以及许多其它事情很有用。

5.4 路由独享守卫

beforeEnter 守卫可直接在路由上进行配置,只在进入路由时触发。而 params、query、hash 改变时不会触发。

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
    	// 返回 false 用于取消当前导航
      	return false
    }
  }
]

beforeEnter 守卫也可以是一个函数数组。

5.5 组件内守卫

顾名思义,就是在组件内直接定义路由导航守卫,会传递给路由配置。

路由组件可以添加如下守卫(setup 组合式 API):

  • onBeforeRouteUpdate 在当前路由改变且该组件被复用时调用。

    举例来说,对于一个带有动态参数的路径 /users/:id,在 /users/1/users/2 之间跳转的时候,由于会渲染同样的 UserDetails 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。

  • onBeforeRouteLeave 在导航离开渲染组件对应的路由时调用。

    这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。

示例代码:

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确定要离开当前页面? 你并未保存更改信息哦!')
  // 取消导航并停留在同一页面上
  if (!answer) return false
})

const userData = ref()

// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
  //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
  if (to.params.id !== from.params.id) {
    userData.value = await fetchUser(to.params.id)
  }
})

5.6 完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 onBeforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 onBeforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter(选项式API)。
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter (选项式API)守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

注:在组件卸载时,所有路由守卫会被移除。

猜你喜欢

转载自blog.csdn.net/qq_39335404/article/details/131498394