【VueRouter】children—子路由的原理和作用

2,282 阅读5分钟

🟢children—子路由是什么

在 Vue.js 中,VueRouter 提供了创建嵌套路由(子路由)的能力,这在构建具有复杂层次结构的单页应用时非常有用。子路由允许你在一个父级路由下定义多个子路由,这些子路由可以共享相同的路径前缀,但各自拥有独立的组件和路径。

子路由(Children Routes)的使用场景

假设你有一个 Users 的父级路由,你可能想在这个路由下展示所有用户列表,并且每个用户都有一个单独的详情页面。在这种情况下,你可以定义一个父级路由和多个子路由,如下所示:

const routes = [
  {
    path: '/users',
    component: UsersComponent, // 这个组件负责展示用户列表
    children: [ // 这里定义子路由
      {
        path: ':userId', // 子路由的路径,其中 :userId 是动态参数
        component: UserDetailsComponent, // 这个组件负责展示用户详情
      },
      {
        path: ':userId/edit', // 编辑用户信息的子路由
        component: EditUserComponent,
      },
    ],
  },
];

子路由的配置

子路由配置在父级路由的 children 数组内。每个子路由都包含一个 path 和一个 component。子路由的 path 是相对于父级路由的路径的,所以当你访问 /users/123 或者 /users/123/edit 时,对应的子路由组件将会被渲染。

使用 <router-view> 标签

在父级组件中,你需要使用 <router-view> 标签来定义子路由组件的渲染位置。如果没有 <router-view>,子路由组件将无法被渲染。
例如,在 UsersComponent 中:

<template>
  <div>
    <h1>User List</h1>
    <ul>
      <!-- 用户列表 -->
    </ul>
    <router-view></router-view> <!-- 子路由组件将在这里被渲染 -->
  </div>
</template>

动态路由参数

子路由可以包含动态参数,如上面示例中的 :userId。这些参数可以在子路由组件中通过 $route.params 访问,这使得你可以在组件中获取和使用这些参数,例如展示特定用户的信息。

🟢为什么要使用子路由

使用子路由在 Vue.js 应用中有很多优势,尤其是在构建具有复杂层级结构的大型应用时。以下是使用子路由的一些主要原因:

  1. 模块化和可维护性: 子路由允许你将应用的路由结构组织成模块化的形式,每个模块可以有自己的子路由。这样,随着应用规模的增长,你可以更容易地管理和扩展路由配置,每个部分都可以独立开发和测试。
  2. 清晰的层次结构: 通过子路由,你可以创建具有明显层次的导航结构。例如,你可能有一个/users路由,其下有/users/:id/users/:id/edit的子路由。这使得 URL 结构更加有意义和直观,用户和开发者都能更容易理解应用的导航布局。
  3. 重用组件和模板: 子路由可以让你在同一个页面的不同区域重用组件。例如,🟥你可能有一个主布局组件,其中包含一个侧边栏和一个内容区域。子路由允许你只替换内容区域的组件,而保持侧边栏和其他公共部分不变,从而提高了组件的复用性和性能。🟥(这是最主要的原因, 如果不把路由放在子路由里, 那么跳转的时候就会到一个全新的页面,而不是改变局部区域)
  4. 减少页面跳转: 当你使用子路由时,用户在应用内的导航通常不会导致整个页面的重新加载。相反,只有相关部分的组件会被更新或替换,这提高了用户体验,因为页面加载更快,过渡更平滑。
  5. 简化状态管理: 在具有多个子路由的组件中,你可以更容易地管理组件间的状态。例如,你可以在一个子路由中保存某个用户的信息,然后在另一个子路由中编辑该信息,而不需要重新加载或检索数据。
  6. URL 的一致性: 子路由有助于保持 URL 的一致性和可预测性。用户可以更容易地通过 URL 分享或书签化应用的特定部分,而无需担心链接的有效性。
  7. 更好的 SEO: 虽然单页应用的 SEO 通常比多页应用更具挑战性,但合理使用子路由可以帮助搜索引擎更好地索引应用的不同部分,特别是当 URL 结构反映了应用的内容层次时。

总之,子路由是 Vue.js 单页应用中组织和导航的重要组成部分,它有助于提高应用的可维护性、性能和用户体验。通过精心设计子路由结构,你可以构建出既强大又直观的用户界面。

🟢子路由的底层原理是什么

子路由的底层原理主要涉及 Vue Router 的工作方式和如何处理嵌套路由的匹配和渲染。在 Vue.js 应用中,Vue Router 作为一个核心库,负责管理应用的路由系统。下面我们将深入探讨子路由的底层原理:

路由匹配

当 Vue Router 初始化时,它会根据你提供的路由配置来构建一个路由图。这个路由图包括了所有定义的路由及其对应的组件和嵌套关系。每当 URL 发生变化时,Vue Router 都会遍历这个路由图,找到与当前 URL 最匹配的路由记录。

什么是路由图

  1. Web应用路由图:在Web开发中,特别是使用如Vue.js、React或Angular这类框架时,路由图指的是描述应用中不同URL与对应组件或视图之间映射关系的结构。这种图帮助开发者理解应用的导航结构和页面之间的关联。

对于Web应用中的路由图,例如在Vue.js中,我们可以将其想象成一个树状结构,其中:

  • 根节点代表应用的入口点,通常是一个默认的主页或登录页面。
  • 分支代表不同的功能模块或页面,每个分支可以有多个子节点。
  • 叶子节点代表具体的页面或组件,它们是用户可以直接访问的最小单位。

例如,一个简单的Vue Router的路由图可能如下所示:

- /
   |
   - /users
      |
      - /users/:id
         |
         - /users/:id/edit

在这个例子中:

  • /是应用的根路由,可能显示主页。
  • /users是一个父级路由,它下面有子路由。
  • /users/:id是一个动态路由,/:id是一个占位符,表示任何数字ID。
  • /users/:id/edit是一个更深层次的子路由,用于编辑特定用户的详细信息。

每个路由条目都与一个组件相关联,当用户导航到特定的URL时,相应的组件将被渲染到页面上。
希望这能帮助你理解“路由图”在不同上下文中可能具有的含义。如果你需要更具体的信息,请提供更详细的上下文或场景。

实际业务路由图展示

假设这是一个中型在线商店的Web应用程序。这样的路由图不仅会包含用户管理部分,还会包括产品目录、购物车、订单处理以及一些公共页面。以下是可能的路由结构:

- /
   |
   - / (首页)
   |
   - /about (关于我们)
   |
   - /contact (联系我们)
   |
   - /products
      |
      - /products (所有产品列表)
      |
      - /products/:category
         |
         - /products/:category/:productId (产品详情)
      |
      - /products/search?q=:query (搜索结果页)
   |
   - /cart (购物车)
   |
   - /checkout (结账流程)
   |
   - /orders
      |
      - /orders (我的订单列表)
      |
      - /orders/:orderId (订单详情)
   |
   - /users
      |
      - /users/login (登录)
      |
      - /users/register (注册)
      |
      - /users/:userId
         |
         - /users/:userId/profile (用户个人资料)
         |
         - /users/:userId/settings (账户设置)
         |
         - /users/:userId/orders (用户订单)
            |
            - /users/:userId/orders/:orderId (用户订单详情)
         |
         - /users/:userId/wishlist (愿望清单)
   |
   - /admin
      |
      - /admin/dashboard (管理员控制台)
      |
      - /admin/products (管理产品)
      |
      - /admin/orders (管理订单)
      |
      - /admin/users (管理用户)

在这个路由图中:

  • **/ (首页)**显示网站的主页面。
  • /about 和 /contact提供公司信息和联系方式。
  • /products相关的路由用于浏览和查看商品。
  • /cart 和 /checkout处理购物车和支付流程。
  • /orders跟踪用户订单状态。
  • /users包含用户相关的路由,如登录、注册和个人资料管理。
  • /admin提供给管理员使用的路由,用于管理产品、订单和用户。

每个路由都有其对应的组件或视图,用于展示特定的信息或处理特定的功能。例如,/users/:userId/profile 路由可能会加载并显示用户的个人资料页面,而 /admin/products 可能会显示一个产品管理界面,允许管理员添加、编辑或删除产品。这样的路由结构可以帮助我们组织和理解大型Web应用的复杂导航结构。

根据什么算法遍历的路由图

Vue Router 在遍历和匹配路由时,并不严格遵循某种特定的经典图遍历算法(如深度优先搜索 DFS 或广度优先搜索 BFS)。相反,它的行为更接近于一种目的驱动的查找机制,其主要目标是在路由图中找到与当前 URL 最佳匹配的路由记录。这一过程可以分为以下几个步骤:

  1. 解析 URL:当浏览器的 URL 发生变化时,Vue Router 首先会解析新的 URL,提取出路径名、查询参数等信息。
  2. 匹配路由规则:Vue Router 维护着一个路由配置数组,每个配置项代表一个可访问的路径。它会遍历这些配置项,试图找到与当前 URL 匹配的第一条规则。匹配过程会考虑静态路径和动态路径(使用占位符如:id)。
  3. 动态参数匹配:在匹配过程中,如果 URL 中包含动态参数,Vue Router 会捕获这些参数的值,并将它们作为对象传递给目标组件。
  4. 嵌套路由匹配:Vue Router 支持嵌套路由,即一个路由可以有子路由。在匹配过程中,它会递归地查找当前匹配路由下的子路由,直到找到最深层的完全匹配为止。
  5. 导航守卫:在完成匹配后,Vue Router 会执行一系列导航守卫(Navigation Guards),包括全局守卫、单个路由独享守卫、组件内的守卫等,这些守卫允许开发者在导航发生之前或之后执行代码,比如进行权限检查或数据预取。
  6. 渲染匹配的组件:一旦通过了所有守卫,Vue Router 将会渲染与最终匹配的路由相对应的组件,并更新视图。

在 Vue Router 的内部实现中,它使用了一个高效的数据结构来存储路由配置,以便快速查找和匹配。具体来说,它使用了一种叫做“最长前缀匹配”(Longest Prefix Match)的技术,这类似于路由表中的查找方法,在网络路由选择算法中常见。这意味着在遍历路由配置时,它会尽量寻找最长的匹配路径,确保精确匹配到最具体的路由规则。
因此,尽管 Vue Router 的遍历逻辑没有直接引用 DFS 或 BFS 算法,但它确实融合了图遍历的基本思想,通过高效的匹配策略来确保路由的正确性和性能。

嵌套路由的匹配

在 Vue Router 中,子路由是在父级路由的 children 属性中定义的。当 Vue Router 在匹配 URL 时,它会先找到与 URL 前缀匹配的父级路由,然后继续在其子路由中查找更详细的匹配。子路由的路径是相对于父级路由的路径的,这意味着它们共享一个共同的路径前缀。

组件渲染

一旦找到最匹配的路由记录,Vue Router 会确定需要渲染哪些组件。在 Vue.js 中,这通常通过 <router-view> 组件实现。<router-view> 是一个特殊的组件,它可以用来显示当前活动的路由组件。在存在嵌套路由的情况下,一个页面可能包含多个 <router-view>,每个 <router-view> 都可以显示不同级别的路由组件。

代码示例

当涉及到嵌套路由时,一个页面可能包含多个<router-view>的概念,可能会显得有些抽象,尤其是如果你刚刚接触Vue Router。让我们通过一个具体的例子来解释这句话:

假设场景:电子商务网站

想象一下你正在构建一个电子商务网站,其中包含以下页面和功能:

  1. 主页(Home.vue):展示最新产品和促销信息。

  2. 产品分类(Categories.vue):列出所有的产品分类,如电子、服装、书籍等。

  3. 产品列表(Products.vue):显示某个分类下的所有产品。

  4. 产品详情(ProductDetails.vue):显示单个产品的详细信息,包括图片、描述和价格。

路由配置

你的Vue Router配置可能如下所示:

  const routes = [
  { path: '/', component: Home },
  { path: '/categories', component: Categories },
  {
   path: '/categories/:categorySlug',
   component: Products,
   children: [
     {
       path: ':productId',
       component: ProductDetails
     }
   ]
  }
  ];

使用<router-view>的页面结构

Categories.vue组件中,你可能希望在页面的某个区域显示分类列表,在另一个区域显示所选分类的产品列表。为了实现这一点,你可以在Categories.vue模板中使用两个<router-view>

  <!-- views/Categories.vue -->
  <template>
  <div>
   <h1>产品分类</h1>
   <!-- 显示分类列表 -->
   <ul>
     <li v-for="category in categories" :key="category.id">
       <router-link :to="{ name: 'products', params: { categorySlug: category.slug } }">
         {{ category.name }}
       </router-link>
     </li>
   </ul>
  
   <!-- 显示所选分类的产品列表 -->
   <router-view></router-view>
  </div>
  </template>

在这个例子中,第一个<router-view>(在Categories.vue的循环列表内)实际上不会显示任何内容,因为它位于一个没有子路由的组件中。但是,第二个<router-view>将显示与当前分类相关的Products.vue组件。
当你点击一个分类时,URL会变为/categories/some-category-slug,这会触发Categories.vue的子路由,Products.vue组件将被渲染在第二个<router-view>的位置。

产品详情页

同样,Products.vue组件可以包含一个<router-view>,用于显示ProductDetails.vue组件:

  <!-- views/Products.vue -->
  <template>
  <div>
   <h2>{{ category.name }}分类下的产品</h2>
   <!-- 显示产品列表 -->
   <ul>
     <li v-for="product in products" :key="product.id">
       <router-link :to="{ name: 'product-details', params: { productId: product.id } }">
         {{ product.name }}
       </router-link>
     </li>
   </ul>
  
   <!-- 显示产品详情 -->
   <router-view></router-view>
  </div>
  </template>

当用户点击一个产品时,URL将变为/categories/some-category-slug/some-product-id,这会触发Products.vue的子路由,ProductDetails.vue组件将被渲染在<router-view>的位置。

总结

通过这种方式,每个<router-view>都充当一个容器,用于显示与当前路由对应的组件。多个<router-view>允许你构建具有复杂结构的页面,其中不同的部分可以独立响应路由变化。这是Vue Router强大和灵活的地方,它使得构建多层次、结构化的单页面应用成为可能。

多级 <router-view>(嵌套)

当一个父级路由包含子路由时,通常在父级组件中使用一个 <router-view> 来显示子路由组件。如果子路由还有自己的子路由,那么在子路由组件中也可以包含 <router-view>,以此类推,形成多级的视图嵌套。

代码示例

当我们构建具有复杂层级结构的单页应用(SPA)时,多级<router-view>的使用变得非常关键。这允许我们创建嵌套的布局和模块化的组件结构,其中每个子路由都可以拥有自己的视图和组件。下面是一个使用多级<router-view>的例子:
首先,我们定义路由配置。假设我们有一个博客应用,其中包含主页、文章列表、单篇文章详情以及文章评论的页面。

// router.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from './views/Home.vue';
import Posts from './views/Posts.vue';
import PostDetail from './views/PostDetail.vue';
import Comments from './views/Comments.vue';

const routes = [
  { path: '/', component: Home },
  {
    path: '/posts',
    component: Posts,
    children: [
      {
        path: ':postId', // 动态路由参数
        component: PostDetail,
        children: [
          {
            path: 'comments',
            component: Comments,
          },
        ],
      },
    ],
  },
];

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

export default router;

接下来,我们设计我们的组件。Home.vuePosts.vue 可以是最基础的列表页面,而 PostDetail.vueComments.vue 则展示详细内容和评论。
Posts.vue中,我们使用<router-view>来嵌套显示PostDetail.vue

<!-- views/Posts.vue -->
<template>
  <div class="posts">
    <!-- 假设这里有一些帖子的列表 -->
    <router-view></router-view> <!-- 显示子路由组件 -->
  </div>
</template>

然后,在PostDetail.vue中,我们再次使用<router-view>来嵌套显示Comments.vue

<!-- views/PostDetail.vue -->
<template>
  <div class="post-detail">
    <h1>{{ $route.params.postId }}</h1>
    <!-- 假设这里有一些关于帖子的详细信息 -->
    <router-view></router-view> <!-- 显示子路由组件 -->
  </div>
</template>

这样,当用户访问 /posts/123/comments 时,Posts.vue 会被渲染,其中的<router-view>将显示PostDetail.vue,而PostDetail.vue中的<router-view>则显示Comments.vue。这种结构允许我们构建层次分明、可扩展的应用程序,同时保持组件的重用性和独立性。

数据传递与状态管理

子路由组件可以通过 Vue Router 的 $route 对象访问当前路由的信息,包括任何动态参数和查询字符串。这使得子组件能够根据 URL 的变化动态更新自身状态。同时,Vue Router 提供了守卫(guards)机制,如 beforeEnter,允许在路由转换之前执行代码,这可以用于数据预加载、权限检查等。

这句话涉及到了Vue Router中几个关键的概念,包括如何在组件中访问路由信息、动态参数和查询字符串,以及Vue Router提供的守卫机制。下面我将详细解释这些概念:

访问当前路由信息

在Vue Router中,每一个组件都可以访问到$route对象,这是一个包含了当前激活路由信息的对象。这个对象包含了诸如pathparams(动态参数)、query(查询字符串参数)等属性。这意味着,无论是在父组件还是子组件中,只要该组件通过<router-view>插入到DOM中,它就可以使用this.$route来获取当前路由的详细信息。
例如,如果你的路由定义如下:

{
  path: '/user/:id',
  component: UserComponent
}

UserComponent中,你可以这样访问动态参数:

export default {
  data() {
    return {
      userId: this.$route.params.id
    };
  },
  watch: {
    '$route.params.id': function(newVal) {
      console.log('New user ID:', newVal);
    }
  }
};

查询字符串

查询字符串参数可以通过this.$route.query访问。例如,如果URL为/search?q=query&category=all,则在组件中可以这样访问:

console.log(this.$route.query.q); // 输出 "query"
console.log(this.$route.query.category); // 输出 "all"

守卫(Guards)

Vue Router提供了多种守卫机制,用于在路由转换发生前执行代码。这些守卫包括:

  • beforeEach:全局前置守卫,对任何路由转换都会生效。
  • beforeRouteEnter:组件级守卫,当路由进入时执行,但此时组件实例还未被创建。
  • beforeRouteUpdate:组件级守卫,当同一组件用于多个路径时,在路由更新时调用。
  • beforeRouteLeave:组件级守卫,当离开路由时调用。
  • beforeEnter:路由配置中的守卫,用于在进入路由之前执行代码。

例如,使用beforeEnter守卫来预加载数据:

const routes = [
  {
    path: '/user/:id',
    component: UserComponent,
    beforeEnter: (to, from, next) => {
      fetchUserData(to.params.id).then(user => {
        // 将数据存储到store或组件的data中
        next();
      });
    }
  }
];

或者使用beforeRouteEnter来初始化组件状态:

export default {
  beforeRouteEnter(to, from, next) {
    // 这里不能访问 this,因为组件实例还未创建
    fetchData().then(data => {
      next(vm => {
        vm.setData(data);
      });
    });
  }
};

通过这些守卫,你可以实现数据预加载、权限检查、状态维护等多种功能,从而增强应用的逻辑和用户体验。

总结

子路由的底层原理涉及了路由匹配算法、组件的条件渲染、以及数据和状态的传递机制。Vue Router 通过这些机制,使得开发者可以构建具有丰富导航结构的单页应用,同时保持良好的性能和用户体验。通过合理的设计和配置,子路由可以极大地提升应用的模块化程度和可维护性。

🟥注意: 子路由的path配置是否需要显式地包含父路由的路径?

在Vue Router中,子路由的path配置并不需要显式地包含父路由的路径。这是因为子路由的路径是相对于其父路由的路径而言的。当你定义子路由时,它们的完整路径会自动组合父路由的路径和子路由自身的路径。

例如:

{
    path: "/latestLost",
    name: "latestLost",
    component: () => import("@/views/LatestLost"),
    children: [
        {
            path: "/item/:id",
            name: "item",
            component: () => import("@/components/ItemCard"),
        },
    ],
}

这里,子路由的path配置为"/item/:id"。但实际上,当你访问这个子路由时,完整的URL路径将会是"/latestLost/item/:id"。这是因为子路由的路径是相对于其父路由的路径"/latestLost"的。

Vue Router会自动将子路由的路径拼接到父路由的路径后面,形成一个完整的路径。这意味着你不需要在子路由的path中重复父路由的路径部分,这既简化了配置,又避免了潜在的错误。

总结来说,子路由的path配置不需要补充上父路由的path,因为Vue Router会自动处理这个问题。在上面的例子中,如果你想直接访问子路由ItemCard,URL将会是http://yourdomain.com/latestLost/item/123,其中123是动态参数id的值。