动态路由使用

API

router.addRoute() 和 router.removeRoute()

如果新增加的路由与当前位置相匹配,就需要你用 router.push()router.replace() 来手动导航,才能显示该新路由

并不是说不能使用导航守卫提供的nextreturn重新导航后不能显示该新路由,而是router.push()router.replace()可以在任何地方使用,不仅限于导航守卫。

router.push() 和 router.replace()

  • router.push(location):将路径添加到浏览器的历史记录中,进行导航。

  • router.replace(location):替换当前的历史记录条目,而不添加新的条目。

  • 适用于非导航守卫中的动态导航。

  • 需要手动控制导航流程。

  • 可能导致导航守卫不一致(在某些复杂场景下)。

  • 会重新执行导航守卫

删除路由

当路由被删除时,所有的别名和子路由也会被同时删除

可以通过name删除,所以请保持name值唯一

添加嵌套路由

1
2
3
4
5
6
router.addRoute({
  name: 'admin',
  path: '/admin',
  component: Admin,
  children: [{ path: 'settings', component: AdminSettings }],
})

查看现有路由

Vue Router 提供了两个功能来查看现有的路由:

  • router.hasRoute():检查路由是否存在。
  • router.getRoutes():获取一个包含所有路由记录的数组。

手动导航的三种方式的区别

router.replace()next({ ...to, replace: true })return { ...to, replace: true }

router.replace()next({ ...to, replace: true }) 在功能上有些相似,但它们的使用场景和细节上有一些区别。

router.replace()

  • 使用场景router.replace() 是一个方法,可以在任何地方使用,不仅限于导航守卫。它会立即执行导航,并用新的路由替换当前的路由,而不会在浏览历史中留下记录。
  • 调用位置:可以在组件方法或导航守卫中使用。记住,如果你需要等待新的路由显示,可以使用 await router.replace()
  • 行为:导航到新的路由并替换当前的路由,旧的路由不会出现在历史记录中。
1
router.replace('/new-path');

NOTE: 在导航守卫中,更推荐使用nextreturn来通过返回新的位置来触发重定向

next({ ...to, replace: true })

  • 使用场景next() 是导航守卫(如 beforeEach)中的特有方法,用于控制导航的过程。next({ ...to, replace: true }) 可以用来重定向并替换当前的路由。
  • 调用位置:只能在导航守卫中使用。
  • 行为:使用 next 方法继续导航过程,并指定重定向目标,同时使用 replace: true 选项来替换当前的路由。
1
2
3
4
5
6
7
router.beforeEach((to, from, next) => {
  if (shouldRedirect) {
    next({ path: '/new-path', replace: true });
  } else {
    next();
  }
});

return { ...to, replace: true }

官网推荐使用return ...替代next(...)

  • 在导航守卫中,返回一个位置对象会告诉 Vue Router 要导航到新的位置。这个返回值会被 Vue Router 处理,并且重新触发导航守卫,使得整个导航过程重新运行。
  • 自动重新触发导航守卫。
  • 确保新路由生效,并且导航流程一致。

区别

  1. 调用位置

    • router.replace() 可以在任何地方调用,包括组件内部的方法、导航守卫等。
    • next({ ...to, replace: true }) 只能在导航守卫中使用。
  2. 导航控制

    • router.replace() 是一个独立的方法调用,立即执行导航。
    • next({ ...to, replace: true }) 是通过导航守卫控制导航流程的一部分。
  3. 返回方式

    • router.replace() 直接执行,不返回控制权。
    • next({ ...to, replace: true }) 是在 beforeEach 等导航守卫中用于控制导航流程的返回值。

代码示例

使用 router.replace()

1
2
3
4
5
6
7
router.beforeEach((to, from, next) => {
  if (shouldRedirect) {
    router.replace('/new-path');
  } else {
    next();
  }
});

使用 next({ ...to, replace: true })

1
2
3
4
5
router.beforeEach((to, from) => {
  if (shouldRedirect) {
    return { path: '/new-path', replace: true };
  }
});

总结

  • 在导航守卫中,使用 next({ ...to, replace: true }) 是更推荐的方式,因为它可以更好地控制导航流程。
  • 在导航守卫之外,使用 router.replace()。它是更通用的方法,可以的任何地方使用。

router.addRoute()

router.replace()的应用场景

router.replace ,用于替换当前的路由。这与 router.push 类似,但不同的是,router.replace 不会向历史记录添加新条目,而是替换当前的历史记录条目。因此,当使用 router.replace 进行导航时,用户无法通过点击浏览器的“返回”按钮回到先前的页面。

以下是一些使用 router.replace 的典型场景:

  1. 登录和注销重定向: 当用户登录成功后,你可能希望将用户重定向到首页或特定的仪表盘页面,并且不希望用户通过浏览器的“返回”按钮回到登录页面。

    1
    2
    
    // 登录成功后的处理
    this.$router.replace({ name: 'dashboard' });
    
  2. 清理查询参数: 如果用户通过带有查询参数的链接访问某个页面,但在页面加载后这些查询参数已经不再需要,可以使用 router.replace 清理 URL。

    1
    2
    3
    4
    
    if (this.$route.query.token) {
      // 处理 token...
      this.$router.replace({ path: this.$route.path, query: null });
    }
    
  3. 表单提交后的重定向: 在表单提交成功后,通常会将用户重定向到一个新页面。为了避免用户通过“返回”按钮重新提交表单,可以使用 router.replace

    1
    2
    3
    
    this.submitForm().then(() => {
      this.$router.replace({ name: 'successPage' });
    });
    
  4. 404 页面重定向: 当用户访问一个不存在的页面时,可以将用户重定向到 404 页面,并使用 router.replace 以防止用户在返回时再次看到错误页面。

    1
    2
    3
    4
    
    // 在路由守卫或组件中
    if (this.pageNotFound) {
      this.$router.replace({ name: 'NotFound' });
    }
    
  5. 基于条件的动态重定向: 在某些情况下,你可能会基于条件来重定向用户。例如,用户访问某个特定页面时,根据用户角色或权限进行重定向。

    1
    2
    3
    
    if (userRole !== 'admin') {
      this.$router.replace({ name: 'home' });
    }
    

NOTE: 在导航守卫中更推荐return ...next(...)

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 在 Vue 组件中使用
export default {
  methods: {
    login() {
      // 模拟登录请求
      setTimeout(() => {
        // 登录成功后重定向到 dashboard 页面
        this.$router.replace({ name: 'dashboard' });
      }, 1000);
    }
  }
}

总之router.replace 它在防止用户回到不希望显示的页面时非常有用。

动态调整应用的路由结构

在 Vue Router 的导航守卫中动态添加或删除路由是一个比较高级的用法,通常在需要基于某些条件(例如用户权限或角色)动态调整应用的路由结构时使用。
这个场景的核心思想是,导航守卫可以在导航过程中对路由进行修改,然后通过返回一个新的位置来触发重定向,而不是直接调用 router.replace()。这是因为在导航守卫中直接调用 router.replace() 可能会导致导航重复触发,从而引起无限循环的问题。

示例场景解释

假设我们有一个应用,其中某些路由只有在特定条件下才存在(例如,用户登录后才可访问的管理页面)。在用户访问某个路由时,我们需要检查该路由是否已经存在,如果不存在则动态添加,然后重定向到该路由。

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 导航守卫中动态添加路由
router.beforeEach(async (to, from, next) => {
  // 判断目标路由是否存在
  if (!hasNecessaryRoute(to)) {
    // 动态生成并添加路由
    const newRoute = generateRoute(to)
    router.addRoute(newRoute)
    
    // 返回新的位置以触发重定向
    next({ ...to, replace: true })
  } else {
    // 正常导航
    next()
  }
})

// 判断目标路由是否已经存在
function hasNecessaryRoute(to) {
  return router.getRoutes().some(route => route.name === to.name)
}

// 根据目标路由动态生成新的路由
function generateRoute(to) {
  // 示例:根据目标路由生成新路由
  return {
    path: to.path,
    name: to.name,
    component: () => import(`@/views/${to.name}.vue`)
  }
}

详细解释

  1. 检查路由是否存在: 在导航守卫中首先检查目标路由是否已经存在,使用 router.getRoutes() 获取当前所有路由,然后通过 some 方法判断目标路由是否存在。

  2. 动态添加路由: 如果目标路由不存在,则通过 generateRoute(to) 动态生成新的路由配置,并使用 router.addRoute() 将新路由添加到路由表中。

  3. 触发重定向: 通过返回新的位置(即 next({ ...to, replace: true }))来触发重定向。这将确保当前导航被取消,然后重新发起一次导航到新的路由,同时使用 replace: true 确保不会向浏览器的历史记录添加新条目。

  4. 正常导航: 如果目标路由已经存在,则调用 next() 进行正常导航。

注意事项

  • 避免无限循环: 通过返回新的位置触发重定向,而不是直接调用 router.replace(),可以有效避免导航守卫中的无限循环问题。
  • 异步加载组件: 动态添加的路由可以使用异步组件加载,以确保在路由被访问时组件才会被加载,优化应用性能。

通过这种方式,可以在导航过程中根据实际需要动态调整路由结构,确保用户始终能够访问到最新的路由配置。

router.replace()理解如何通过返回新的位置触发重定向而避免无限循环问题,可以从 Vue Router 的导航守卫执行逻辑入手。

导航守卫中的无限循环问题

在导航守卫中直接调用 router.replace() 可能会导致无限循环。这是因为 router.replace() 会立即触发一次新的导航,而这个新的导航会再次触发导航守卫,导致导航守卫再次执行 router.replace(),从而形成无限循环。

通过返回新的位置避免无限循环

当我们在导航守卫中返回新的位置而不是直接调用 router.replace() 时,Vue Router 会认为这是一次新的导航请求。这个新的导航请求会重新评估所有导航守卫,但因为我们在返回新的位置时不会立即触发导航(而是等待当前导航流程完成),所以不会形成无限循环。

代码解析

下面是一个具体的代码示例以及解析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 导航守卫中动态添加路由
router.beforeEach(async (to, from, next) => {
  // 判断目标路由是否存在
  if (!hasNecessaryRoute(to)) {
    // 动态生成并添加路由
    const newRoute = generateRoute(to)
    router.addRoute(newRoute)
    
    // 返回新的位置以触发重定向,避免直接调用 router.replace()
    next({ ...to, replace: true })
  } else {
    // 正常导航
    next()
  }
})

// 判断目标路由是否已经存在
function hasNecessaryRoute(to) {
  return router.getRoutes().some(route => route.name === to.name)
}

// 根据目标路由动态生成新的路由
function generateRoute(to) {
  // 示例:根据目标路由生成新路由
  return {
    path: to.path,
    name: to.name,
    component: () => import(`@/views/${to.name}.vue`)
  }
}

详细解析

  1. 判断目标路由是否存在

    • 使用 router.getRoutes() 获取当前所有路由,并通过 some 方法判断目标路由是否存在。
  2. 动态添加路由

    • 如果目标路由不存在,使用 generateRoute(to) 动态生成新的路由配置,并使用 router.addRoute(newRoute) 将新路由添加到路由表中。
  3. 返回新的位置

    • 通过 next({ ...to, replace: true }) 返回新的位置。这告诉 Vue Router 当前导航已经结束,并需要导航到新的位置。
    • replace: true 确保不会向浏览器的历史记录添加新条目。
  4. 重新触发导航守卫

    • 返回新的位置后,Vue Router 会重新评估导航守卫。但由于我们不是立即触发导航,而是等待当前导航流程完成,所以不会出现无限循环问题。
  5. 正常导航

    • 如果目标路由已经存在,直接调用 next() 进行正常导航。

避免无限循环的关键点

  • 等待当前导航流程完成: 返回新的位置而不是立即调用 router.replace() 允许当前的导航流程完成后再触发新的导航。这避免了直接在导航守卫中嵌套触发新的导航请求,避免了无限循环。

  • 返回新的位置: 返回新的位置通过 next({ ...to, replace: true }) 告诉 Vue Router 重定向到新的位置,而不是立即发起一个新的导航请求。这样可以确保新的导航流程在当前流程完成后才会开始。

通过这种方式,可以动态调整路由配置,同时避免导航守卫中的无限循环问题。无限循环问题

路由记录和元信息

Vue Router 的标准路由记录和 meta 类型是设计用来管理和配置路由信息的。这些设计使得在应用中处理路由变得更加灵活和强大。以下是对 Vue Router 的标准路由记录和 meta 类型的详细说明:

标准路由记录类型 (RouteRecordRaw)

RouteRecordRaw 是 Vue Router 中定义路由的基本类型。它用于定义一个路由配置的原始记录。主要字段包括:

  1. path: string

    • 路由的路径。可以是静态路径或者带有动态参数的路径(例如 /user/:id)。
  2. name: string | undefined

    • 路由的名称。这个名称用于路由导航和重定向。它可以是 undefined
  3. component: Component | AsyncComponent | undefined

    • 这个路由匹配时加载的组件。可以是一个组件对象,也可以是异步组件加载函数。
  4. redirect: string | Location | Function | undefined

    • 路由重定向的目标路径。如果设置了重定向,当访问这个路由时,会自动导航到指定的目标路径。
  5. children: RouteRecordRaw[] | undefined

    • 子路由配置。这个路由的子路由配置可以通过嵌套的 RouteRecordRaw 数组来定义。如果没有子路由,这个字段可以是 undefined
  6. meta: Record<string, any>

    • 路由的元信息。meta 是一个对象,可以包含任意的自定义信息,比如权限、标题等。这些信息不会影响路由的匹配,而是用于应用的其他逻辑。
  7. beforeEnter: RouteEnterGuard | undefined

    • 进入路由前的导航守卫函数。用于在路由进入前进行权限检查或其他逻辑处理。
  8. props: boolean | object | Function | undefined

    • 控制如何将路由参数传递给组件。可以是布尔值、对象、函数或者 undefined

meta 类型

meta 是路由记录中的一个字段,用于存储与路由相关的任意元数据。你可以根据需要自定义 meta 的内容。常见的字段包括:

  1. title: string | undefined

    • 路由的标题,通常用于页面标题或面包屑导航。
  2. requiresAuth: boolean | undefined

    • 是否需要认证。用于控制访问权限,比如只有经过认证的用户才能访问该路由。
  3. roles: string[] | undefined

    • 访问该路由需要的角色列表。用于权限控制。
  4. icon: string | undefined

    • 路由的图标。通常用于侧边栏或导航菜单。
  5. hidden: boolean | undefined

    • 是否在导航菜单中隐藏该路由。
  6. keepAlive: boolean | undefined

    • 是否缓存该路由组件。通常用于优化性能,避免重复加载组件。
  7. alwaysShow: boolean | undefined

    • 在多级路由中,是否始终显示该路由的父级菜单。
  8. activePath: string | undefined

    • 用于设置路由激活状态的路径,通常用于动态高亮菜单项。
  9. showLink: boolean | undefined

    • 是否在导航菜单中显示该路由。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { RouteRecordRaw } from 'vue-router';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/home',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: {
      title: 'Home Page',
      requiresAuth: false,
      icon: 'home',
      hidden: false,
    },
  },
  {
    path: '/profile',
    name: 'Profile',
    component: () => import('@/views/Profile.vue'),
    meta: {
      title: 'User Profile',
      requiresAuth: true,
      icon: 'user',
      hidden: false,
    },
    children: [
      {
        path: 'settings',
        name: 'ProfileSettings',
        component: () => import('@/views/ProfileSettings.vue'),
        meta: {
          title: 'Profile Settings',
          requiresAuth: true,
          icon: 'settings',
        },
      },
    ],
  },
];

总结

  • RouteRecordRaw 类型定义了一个路由记录的基本结构,包括路径、名称、组件、重定向、子路由、元信息等。
  • meta 字段用于存储与路由相关的任意元数据,方便应用进行各种自定义配置和逻辑处理。

懒加载不使用defineAsyncComponent

根据警告信息,Vue Router 建议使用懒加载组件时直接传递 import 语句,而不是使用 defineAsyncComponent。这是因为 Vue Router 内部会处理组件的懒加载逻辑,因此不需要在外部包装一层 defineAsyncComponent

修改代码

defineAsyncComponent 替换为直接使用 import 语句即可。

修改前

假设你在路由配置中使用 defineAsyncComponent,代码可能如下:

1
2
3
4
5
6
7
8
9
import { defineAsyncComponent } from 'vue';

const routes = [
  {
    path: '/system/menu',
    component: defineAsyncComponent(() => import('@/views/system/menu.vue')),
  },
  // 其他路由
];

修改后

defineAsyncComponent 替换为直接使用 import

1
2
3
4
5
6
7
const routes = [
  {
    path: '/system/menu',
    component: () => import('@/views/system/menu.vue'),
  },
  // 其他路由
];

示例

下面是一个完整的路由配置文件示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/system/menu',
    component: () => import('@/views/system/menu.vue'),
  },
  // 其他路由
];

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

export default router;

总结

defineAsyncComponent 替换为直接使用 import 语句以避免警告,并简化路由配置。这样可以更好地遵循 Vue Router 的建议和最佳实践。

动态导入组件

使用 import.meta.glob 是处理动态导入的一个有效方法,它可以在构建时自动解析和加载匹配的模块。这种方式是 Vite 提供的,用于处理动态模块导入,支持静态分析和构建优化。它适用于你需要根据路径动态加载组件的情况。

使用 import.meta.glob

示例代码

  1. 定义和使用动态模块导入

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    // utils.ts
    import { log } from "@/plugins/logger";
    import { MenuItem } from "@/types/component/menuItem";
    import { RouteRecordRaw } from "vue-router";
    
    // 使用 import.meta.glob 加载所有 Vue 组件
    const modules = import.meta.glob('@/views/**/*.vue');
    
    // 根据路径加载组件
    function loadComponent(componentPath: string) {
      const path = `@/views/${componentPath}`;
      if (modules[path]) {
        return modules[path];
      } else {
        return () => Promise.reject('Component not found');
      }
    }
    
    // 转换 MenuItem 到 RouteRecordRaw
    function convertToRoute(menuItem: MenuItem): RouteRecordRaw {
      const route: RouteRecordRaw = {
        path: menuItem.path,
        name: menuItem.name,
        component: menuItem.component ? loadComponent(menuItem.component) : undefined,
        redirect: menuItem.redirect,
        meta: menuItem.meta as Record<string, any>,
        // 递归调用convertToRoute
        children: menuItem.children ? menuItem.children.map(convertToRoute) : []
      };
      return route;
    }
    
    // 遍历路由数据并添加到 router
    export function addRoutes(menus: MenuItem[]) {
      menus.forEach((menuItem) => {
        const route = convertToRoute(menuItem);
        if (route.name && !router.hasRoute(route.name)) {
          router.addRoute(route);
          log("动态添加路由: name= %s, path= %s", route.name, route.path);
        }
      });
    }
    

说明

  • import.meta.glob:这个方法会返回一个包含匹配文件的对象,键是文件路径,值是一个函数,这个函数会返回一个动态导入的 Promise。
  • loadComponent 函数:通过检查 modules 对象中是否包含指定路径来动态加载组件。如果路径存在于 modules 中,它将返回对应的模块,否则返回一个拒绝的 Promise。

注意事项

  1. 路径匹配: 确保 import.meta.glob 中的路径匹配模式正确。@/views/**/*.vue 匹配 src/views 目录下的所有 Vue 文件及其子目录。

  2. 路径映射: 路径应与 import.meta.glob 中定义的匹配模式一致。@/views/${componentPath} 应该和实际路径匹配。

  3. 组件导入: 确保 modules 中定义的路径与实际组件路径一致,否则会导致找不到组件。

  4. 构建优化import.meta.glob 在构建时会优化模块加载,避免在运行时解析路径。

通过这种方式,你可以在运行时动态加载和注册 Vue 组件,适合于需要动态导入大量组件的场景。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计