跳至主要内容
版本:v8

Vue 导航

本指南介绍了在使用 Ionic 和 Vue 构建的应用中路由的工作原理。

IonRouterOutlet 组件在幕后使用了流行的 Vue Router 库。使用 Ionic 和 Vue Router,您可以创建具有丰富页面过渡的多页面应用。

您对使用 Vue Router 进行路由的所有知识都适用于 Ionic Vue。让我们来看看 Ionic Vue 应用的基础知识以及路由是如何与之协作的。

简要说明

在阅读本指南时,您可能会注意到,大多数概念与在没有 Ionic 框架的情况下在 Vue Router 中找到的概念非常相似。您的观察是正确的!Ionic Vue 利用了 Vue Router 的最佳部分,以使使用 Ionic 框架构建应用的过渡尽可能地无缝。因此,我们建议尽可能地依靠 Vue Router 功能,而不是尝试构建自己的路由解决方案。

一个简单的路由

这是一个示例路由配置,它定义了一个到 “/home” URL 的单个路由。当您访问 “/home” 时,路由将呈现 HomePage 组件。

router/index.ts

import { createRouter, createWebHistory } from '@ionic/vue-router';
import { RouteRecordRaw } from 'vue-router';
import HomePage from '@/views/Home.vue';

const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: HomePage,
},
];

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

export default router;

在应用的初始加载时,应用将呈现 HomePage 组件,因为这是在此处配置的。

处理重定向

如果我们想要在初始加载时到达不同的路径怎么办?为此,我们可以使用路由重定向。重定向的工作方式与典型的路由对象相同,但只包含一些不同的键

const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: HomePage,
},
];

在我们的重定向中,我们查找应用的索引路径。然后,如果我们加载它,我们将重定向到 home 路由。

这一切都很好,但如何真正导航到一个路由?为此,我们可以使用 router-link 属性。让我们创建一个新的路由设置

const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: HomePage,
},
{
path: '/detail',
name: 'Detail',
component: DetailPage,
},
];

假设我们从 home 路由开始,并且我们想要添加一个按钮来将我们带到 detail 路由。我们可以使用以下 HTML 来导航到 detail 路由

<ion-button router-link="/detail">Go to detail</ion-button>

我们也可以通过使用路由 API 在我们的应用中以编程方式导航

<template>
<ion-page>
<ion-content>
<ion-button @click="() => router.push('/detail')">Go to detail</ion-button>
</ion-content>
</ion-page>
</template>

<script lang="ts">
import { IonButton, IonContent, IonPage } from '@ionic/vue';
import { defineComponent } from 'vue';
import { useRouter } from 'vue-router';

export default defineComponent({
name: 'HomePage',
components: {
IonButton,
IonContent,
IonPage,
},
setup() {
const router = useRouter();
return { router };
},
});
</script>

两种选项都提供了相同的导航机制,只是适合不同的用例。

router-link 属性可以设置在任何 Ionic Vue 组件上,当单击组件时,路由器将导航到指定的路由。router-link 属性接受字符串值以及命名路由,就像 Vue Router 中的 router.push 一样。为了获得额外的控制,还可以设置 router-directionrouter-animation 属性。

router-direction 属性接受 forwardbacknone 的值,用于控制页面过渡的方向。

router-animation 属性接受一个 AnimationBuilder 函数,用于提供一个自定义页面过渡,该过渡仅在单击提供它的组件时使用。AnimationBuilder 类型是一个返回 Ionic 动画实例的函数。有关在 Ionic Vue 中使用动画的更多信息,请参阅 动画文档

<ion-button router-link="/page2" router-direction="back" :router-animation="myAnimation">Click Me</ion-button>

使用 router-link 的一个缺点是,您无法在导航之前运行自定义代码。这使得在导航之前发出网络请求等任务变得困难。您可以直接使用 Vue Router,但这样您将失去控制页面过渡的能力。这就是 useIonRouter 实用程序有帮助的地方。

useIonRouter 实用程序是一个函数,它提供以编程方式进行导航的方法,同时完全控制页面过渡。这使得在导航之前运行自定义代码变得容易。

第一个示例允许我们使用自定义页面过渡将新页面推入堆栈

import { defineComponent } from 'vue';
import { useIonRouter } from '@ionic/vue';
import { customAnimation } from '@/animations/customAnimation';

export default defineComponent({
...,
setup() {
const ionRouter = useIonRouter();

ionRouter.push('/page2', customAnimation);
}
});

useIonRouter 提供便利的 pushreplacebackforward 方法,以方便使用常见的导航操作。它还提供了一个 navigate 方法,该方法可用于更复杂的导航场景

import { defineComponent } from 'vue';
import { useIonRouter } from '@ionic/vue';
import { customAnimation } from '@/animations/customAnimation';

export default defineComponent({
...,
setup() {
const ionRouter = useIonRouter();

ionRouter.navigate('/page2', 'forward', 'replace', customAnimation);
}
});

上面的示例让应用使用使用向前方向的自定义动画导航到 /page2。此外,replace 值确保在导航时应用替换当前历史记录条目。

注意

useIonRouter 使用 Vue 的 inject() 函数,只能在您的 setup() 函数中使用。

有关更多详细信息以及类型信息,请参阅 useIonRouter 文档

Vue Router 有一个 router.go 方法,允许开发人员在应用历史记录中向前或向后移动。让我们来看一个例子。

假设您有以下应用历史记录

/pageA --> /pageB --> /pageC

如果您要在 /pageC 上调用 router.go(-2),您将被带回 /pageA。如果您然后调用 router.go(2),您将被带到 /pageC

router.go() 的一个关键特征是它期望您的应用历史记录是线性的。这意味着 router.go() 不应在使用非线性路由的应用中使用。有关更多信息,请参阅 线性路由与非线性路由

延迟加载路由

我们当前的路由设置方式使它们在加载应用时包含在同一个初始块中,这并不总是理想的。相反,我们可以设置我们的路由,以便在需要时加载组件

const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: HomePage,
},
{
path: '/detail',
name: 'Detail',
component: () => import('@/views/DetailPage.vue'),
},
];

在这里,我们拥有与之前相同的设置,只是这次 DetailPage 被导入调用所取代。这将导致 DetailPage 组件不再是应用加载时请求的块的一部分。

线性路由与非线性路由

线性路由

如果您已经构建了一个使用路由的 Web 应用,那么您可能之前使用过线性路由。线性路由意味着您可以通过推入和弹出页面来向前或向后移动应用历史记录。

以下是在移动应用中线性路由的示例

此示例中的应用历史记录具有以下路径

Accessibility --> VoiceOver --> Speech

当我们按下后退按钮时,我们会遵循相同的路由路径,但方向相反。线性路由的优势在于它可以实现简单且可预测的路由行为。这也意味着我们可以使用 Vue Router API,例如 router.go()

线性路由的缺点是它不支持复杂的用户体验,例如选项卡视图。这就是非线性路由发挥作用的地方。

非线性路由

非线性路由是一个概念,对于许多学习使用 Ionic 构建移动应用程序的 Web 开发人员来说可能很陌生。

非线性路由意味着用户应该返回到的视图不一定是在屏幕上显示的先前视图。

以下是非线性路由的示例

在上面的示例中,我们从 "Originals" 选项卡开始。点击一张卡片会将我们带到 "Originals" 选项卡内的 "Ted Lasso" 视图。

从这里,我们切换到 "Search" 选项卡。然后,我们再次点击 "Originals" 选项卡,并被带回到 "Ted Lasso" 视图。此时,我们已经开始使用非线性路由。

为什么这是非线性路由?我们之前所在的视图是 "Search" 视图。但是,在 "Ted Lasso" 视图上按下后退按钮应该将我们带回到根 "Originals" 视图。之所以会发生这种情况,是因为移动应用程序中的每个选项卡都被视为一个独立的堆栈。 使用选项卡 部分将更详细地介绍这一点。

如果点击后退按钮只是从 "Ted Lasso" 视图调用 router.go(-1),我们将被带回到 "Search" 视图,这并不正确。

非线性路由允许实现复杂的用户流程,这是线性路由无法处理的。但是,某些线性路由 API,例如 router.go(),不能在这种非线性环境中使用。这意味着在使用选项卡或嵌套出口时,不应使用 router.go()

我应该选择哪一个?

我们建议在需要添加非线性路由之前,尽可能保持应用程序的简单性。非线性路由非常强大,但也给移动应用程序带来了相当大的复杂性。

非线性路由最常见的两种用法是与选项卡和嵌套的 ion-router-outlet 结合使用。我们建议仅当您的应用程序满足选项卡或嵌套路由出口用例时才使用非线性路由。

有关选项卡的更多信息,请参见 使用选项卡

有关嵌套路由出口的更多信息,请参见 嵌套路由

共享 URL 与嵌套路由

在设置路由时,一个常见的混淆点是决定使用共享 URL 还是嵌套路由。本指南的这一部分将解释两者并帮助您决定使用哪一个。

共享 URL

共享 URL 是一种路由配置,其中路由具有共同的 URL 部分。以下是一个共享 URL 配置的示例

const routes: Array<RouteRecordRaw> = [
{
path: '/dashboard',
component: DashboardMainPage,
},
{
path: '/dashboard/stats',
component: DashboardStatsPage,
},
];

上面的路由被认为是 "共享" 的,因为它们重用了 URL 中的 dashboard 部分。

嵌套路由

嵌套路由是一种路由配置,其中路由被列为其他路由的子路由。以下是一个嵌套路由配置的示例

const routes: Array<RouteRecordRaw> = [
{
path: '/dashboard/:id',
component: DashboardRouterOutlet,
children: [
{
path: '',
component: DashboardMainPage,
},
{
path: 'stats',
component: DashboardStatsPage,
},
],
},
];

上面的路由是嵌套的,因为它们位于父路由的 children 数组中。请注意,父路由渲染了 DashboardRouterOutlet 组件。当您嵌套路由时,您需要渲染另一个 ion-router-outlet 实例。

我应该选择哪一个?

当您希望从页面 A 转到页面 B 同时在 URL 中保留两个页面之间的关系时,共享 URL 非常有用。在前面的示例中,/dashboard 页面上的按钮可以转到 /dashboard/stats 页面。由于 a) 页面过渡和 b) url,两个页面之间的关系得以保留。

当您希望在出口 A 中渲染内容,同时在嵌套出口 B 中渲染子内容时,应该使用嵌套路由。您将遇到的最常见的用例是选项卡。当您加载一个选项卡 Ionic 启动应用程序时,您将在第一个 ion-router-outlet 中看到渲染的 ion-tab-barion-tabs 组件。ion-tabs 组件渲染另一个 ion-router-outlet,它负责渲染每个选项卡的内容。

在移动应用程序中,嵌套路由只有很少的用例。如有疑问,请使用共享 URL 路由配置。我们强烈建议不要在选项卡以外的上下文中使用嵌套路由,因为它会很快使您的应用程序导航变得混乱。

使用选项卡

在使用选项卡时,Ionic Vue 需要一种方法来知道哪个视图属于哪个选项卡。IonTabs 组件在这里非常有用,但让我们看看它的路由设置是什么样的

const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/tabs/tab1',
},
{
path: '/tabs/',
component: Tabs,
children: [
{
path: '',
redirect: 'tab1',
},
{
path: 'tab1',
component: () => import('@/views/Tab1.vue'),
},
{
path: 'tab2',
component: () => import('@/views/Tab2.vue'),
},
{
path: 'tab3',
component: () => import('@/views/Tab3.vue'),
},
],
},
];

在这里,我们的 tabs 路径加载 Tabs 组件。我们提供了每个选项卡作为 children 数组中的路由对象。在这个示例中,我们调用路径 tabs,但这可以自定义。

让我们先看一下我们的 Tabs 组件

<template>
<ion-page>
<ion-tabs>
<ion-router-outlet></ion-router-outlet>
<ion-tab-bar slot="bottom">
<ion-tab-button tab="tab1" href="/tabs/tab1">
<ion-icon :icon="triangle" />
<ion-label>Tab 1</ion-label>
</ion-tab-button>

<ion-tab-button tab="tab2" href="/tabs/tab2">
<ion-icon :icon="ellipse" />
<ion-label>Tab 2</ion-label>
</ion-tab-button>

<ion-tab-button tab="tab3" href="/tabs/tab3">
<ion-icon :icon="square" />
<ion-label>Tab 3</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-page>
</template>

<script lang="ts">
import { IonTabBar, IonTabButton, IonTabs, IonLabel, IonIcon, IonPage, IonRouterOutlet } from '@ionic/vue';
import { ellipse, square, triangle } from 'ionicons/icons';

export default {
name: 'Tabs',
components: {
IonLabel,
IonTabs,
IonTabBar,
IonTabButton,
IonIcon,
IonPage,
IonRouterOutlet,
},
setup() {
return {
ellipse,
square,
triangle,
};
},
};
</script>

如果您以前使用过 Ionic Framework,这应该很熟悉。我们创建一个 ion-tabs 组件并提供一个 ion-tab-barion-tab-bar 提供 ion-tab-button 组件,每个组件都有一个 tab 属性,该属性与其在路由配置中的对应选项卡相关联。我们还提供一个 ion-router-outlet 来让 ion-tabs 有一个出口来渲染不同的选项卡视图。

Ionic 中的选项卡是如何工作的

Ionic 中的每个选项卡都被视为一个单独的导航堆栈。这意味着,如果您在应用程序中拥有三个选项卡,那么每个选项卡都有自己的导航堆栈。在每个堆栈中,您可以向前导航(推送视图)和向后导航(弹出视图)。

请注意,此行为与其他基于 Web 的 UI 库中的大多数选项卡实现不同。其他库通常将选项卡管理为一个单一的历史堆栈。

由于 Ionic 专注于帮助开发人员构建移动应用程序,因此 Ionic 中的选项卡被设计为尽可能地与本机移动选项卡相匹配。因此,Ionic 中选项卡的某些行为可能与您在其他 UI 库中看到的选项卡实现有所不同。继续阅读以了解有关这些差异的一些内容。

选项卡中的子路由

当将其他路由添加到选项卡时,您应该将它们写为具有父选项卡作为路径前缀的兄弟路由。下面的示例将 /tabs/tab1/view 路由定义为 /tabs/tab1 路由的兄弟路由。由于此新路由具有 tab1 前缀,它将在 Tabs 组件内渲染,并且选项卡 1 将仍然在 ion-tab-bar 中被选中。

const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/tabs/tab1',
},
{
path: '/tabs/',
component: Tabs,
children: [
{
path: '',
redirect: 'tab1',
},
{
path: 'tab1',
component: () => import('@/views/Tab1.vue'),
},
{
path: 'tab1/view',
component: () => import('@/views/Tab1View.vue'),
},
{
path: 'tab2',
component: () => import('@/views/Tab2.vue'),
},
{
path: 'tab3',
component: () => import('@/views/Tab3.vue'),
},
],
},
];

在选项卡之间切换

由于每个选项卡都是自己的导航堆栈,因此重要的是要注意这些导航堆栈永远不应该交互。这意味着选项卡 1 中不应该有按钮将用户路由到选项卡 2。换句话说,选项卡只能通过用户点击选项卡栏中的选项卡按钮来更改。

在实践中,一个很好的例子是 iOS App Store 和 Google Play Store 移动应用程序。这两个应用程序都提供了选项卡界面,但都没有将用户路由到跨选项卡。例如,iOS App Store 应用程序中的 "Games" 选项卡从不会将用户引导到 "Search" 选项卡,反之亦然。

让我们看几个关于选项卡的常见错误。

多个选项卡引用的设置选项卡

一个常见的做法是创建一个设置视图作为它自己的选项卡。如果开发人员需要展示多个嵌套的设置菜单,这很好。但是,其他选项卡永远不应该尝试路由到设置选项卡。正如我们上面提到的,设置选项卡被激活的唯一方法是用户点击相应的选项卡按钮。

如果您发现您的选项卡需要引用设置选项卡,我们建议使用 ion-modal 将设置视图设为模态视图。这是在 iOS App Store 应用程序中的一种做法。通过这种方法,任何选项卡都可以展示模态视图,而不会违反每个选项卡都是一个独立堆栈的移动选项卡模式。

下面的示例显示了 iOS App Store 应用程序如何从多个选项卡展示 "Account" 视图。通过在模态视图中展示 "Account" 视图,该应用程序可以在移动选项卡最佳实践中工作,以在多个选项卡中展示相同的视图。

跨选项卡重用视图

另一个常见的做法是在多个选项卡中展示相同的视图。开发人员通常试图通过将视图包含在一个选项卡中,让其他选项卡路由到该选项卡来实现。正如我们上面提到的,这违反了移动选项卡模式,应该避免。

相反,我们建议在每个选项卡中都有路由来引用同一个组件。这是在 Spotify 等流行应用程序中使用的一种做法。例如,您可以从 "Home"、"Search" 和 "Your Library" 选项卡访问专辑或播客。访问专辑或播客时,用户会停留在该选项卡中。该应用程序通过为每个选项卡创建路由并在代码库中共享一个公共组件来实现这一点。

下面的示例展示了 Spotify 应用程序如何重用同一个专辑组件在多个选项卡中展示内容。请注意,每个屏幕截图都展示了同一个专辑,但来自不同的选项卡。

Home 选项卡Search 选项卡

组件

IonRouterOutlet

IonRouterOutlet 组件提供一个容器来渲染你的视图。它类似于其他 Vue 应用程序中的 RouterView 组件,除了 IonRouterOutlet 可以渲染多个页面在同一个出口的 DOM 中。当组件在 IonRouterOutlet 中渲染时,我们认为这是一个 Ionic 框架的 “页面”。路由出口容器控制页面之间的过渡动画,以及控制页面何时被创建和销毁。这有助于维护视图之间的状态,当在它们之间来回切换时。

在模板中设置 IonRouterOutlet 时,不应该在它里面提供任何内容。虽然 IonRouterOutlet 可以嵌套在子组件中,但我们建议不要这样做,因为它通常会使应用程序中的导航变得混乱。有关更多信息,请参见 共享 URL 与嵌套路由

IonPage

IonPage 组件将每个视图包装在 Ionic Vue 应用程序中,并允许页面过渡和堆栈导航正常工作。使用路由导航到的每个视图都必须包含一个 IonPage 组件。

<template>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Home</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">Hello World</ion-content>
</ion-page>
</template>

<script lang="ts">
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
import { defineComponent } from 'vue';

export default defineComponent({
components: {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
},
});
</script>

通过 IonModalIonPopover 展示的组件通常不需要 IonPage 组件,除非你需要一个包装元素。在这种情况下,我们建议使用 IonPage,以便组件尺寸仍然可以正确计算。

函数

useIonRouter

useIonRouter(): UseIonRouterResult

返回 Ionic 路由器实例,包含用于导航、自定义页面过渡和原生功能的路由上下文的 API 方法。此函数可以与 Vue 中的 useRouter 函数结合使用。

有关示例用法,请参阅我们的 实用函数

URL 参数

让我们扩展我们最初的路由示例,以展示如何使用 URL 参数

const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: HomePage,
},
{
path: '/detail/:id',
name: 'Detail',
component: DetailPage,
},
];

注意,我们现在在 detail 路径字符串的末尾添加了 :id。URL 参数是我们路由路径的动态部分。当用户导航到一个 URL,例如 /details/1 时,"1" 将被保存到名为 "id" 的参数中,该参数可以在路由渲染时在组件中访问。

让我们看看如何在组件中使用它

<template>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Details</ion-title>
</ion-toolbar>
</ion-header>

<ion-content> Detail ID: {{ id }} </ion-content>
</ion-page>
</template>

<script lang="ts">
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
import { defineComponent } from 'vue';
import { useRoute } from 'vue-router';

export default defineComponent({
name: 'Detail',
components: {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
},
setup() {
const route = useRoute();
const { id } = route.params;
return { id };
},
});
</script>

我们的 route 变量包含当前路由的实例。它还包含我们传入的任何参数。我们可以从这里获取 id 参数并在屏幕上显示它。

路由历史

Vue 路由器附带一个可配置的历史记录模式。让我们看看不同的选项以及你可能想要使用每个选项的原因。

  • createWebHistory: 此选项创建 HTML5 历史记录。它利用历史记录 API 来实现 URL 导航,而无需页面重新加载。这是单页面应用程序中最常见的历史记录模式。如有疑问,请使用 createWebHistory

  • createWebHashHistory: 此选项向你的 URL 添加一个哈希 (#)。这对于没有主机或你无法完全控制服务器路由的 Web 应用程序非常有用。搜索引擎有时会忽略哈希片段,因此如果 SEO 对你的应用程序很重要,你应该使用 createWebHistory 而不是它。

  • createMemoryHistory: 此选项创建基于内存的历史记录。这主要用于处理服务器端渲染 (SSR)。

更多信息

有关使用 Vue 路由器在 Vue 中进行路由的更多信息,请查看他们的文档,地址为 http://router.vuejs.ac.cn/