Skip to main content

React 导航

本指南介绍了路由如何在使用 Ionic 和 React 构建的应用中工作。

🌐 This guide covers how routing works in an app built with Ionic and React.

IonReactRouter 在底层使用了流行的 React Router 库。借助 Ionic 和 React Router,你可以创建具有丰富页面过渡效果的多页面应用。

你关于使用 React Router 进行路由的所有知识都可以应用到 Ionic React 中。让我们看看 Ionic React 应用的基础知识以及它是如何进行路由的。

🌐 Everything you know about routing using React Router carries over into Ionic React. Let's take a look at the basics of an Ionic React app and how routing works with it.

Ionic React 中的路由

🌐 Routing in Ionic React

这是一个示例 App 组件,它定义了到 "/dashboard" URL 的单一路由。当你访问 "/dashboard" 时,该路由会渲染 DashboardPage 组件。

🌐 Here is a sample App component that defines a single route to the "/dashboard" URL. When you visit "/dashboard", the route renders the DashboardPage component.

App.tsx

const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/dashboard" component={DashboardPage} />
<Redirect exact from="/" to="/dashboard" />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);

Route 之后,我们定义默认的 Redirect,当用户访问应用的根 URL("/")时,它会将他们重定向到 "/dashboard" URL。

🌐 Directly after the Route, we define our default Redirect, which, when a user visits the root URL of the app ("/"), it redirects them to the "/dashboard" URL.

重定向也设置了 exact 属性,这意味着 URL 必须精确匹配 from 属性(或者如果在 Route 上使用了 exact,则匹配 path 属性)才能匹配此路由。如果没有它,这个重定向会在每个路由上渲染,因为每个路由都是以“/”开头的。

🌐 The redirect also has the exact prop set, which means the URL has to match the from prop (or the path prop if exact was used on a Route) precisely for this route to be a match. Without it, this redirect would render for every route, since every route begins with "/".

你还可以根据条件以编程方式从路由的渲染方法进行重定向,例如检查用户是否经过身份验证:

🌐 You can also programmatically redirect from a Route's render method based on a condition, like checking if a user is authed or not:

<Route
exact
path="/dashboard"
render={(props) => {
return isAuthed ? <DashboardPage {...props} /> : <LoginPage />;
}}
/>

IonReactRouter

IonReactRouter 组件封装了来自 React Router 的传统 BrowserRouter 组件,并为应用设置了路由。因此,请使用 IonReactRouter 替代 BrowserRouter。你可以将任何 props 传递给 IonReactRouter,它们将被传递到底层的 BrowserRouter

🌐 The IonReactRouter component wraps the traditional BrowserRouter component from React Router, and sets the app up for routing. Therefore, use IonReactRouter in place of BrowserRouter. You can pass in any props to IonReactRouter and they will be passed down to the underlying BrowserRouter.

嵌套路由

🌐 Nested Routes

在仪表板页面内,我们定义了与应用的此特定部分相关的更多路由:

🌐 Inside the Dashboard page, we define more routes related to this specific section of the app:

仪表板页面.tsx

const DashboardPage: React.FC = () => {
return (
<IonPage>
<IonRouterOutlet>
<Route exact path="/dashboard" component={UsersListPage} />
<Route path="/dashboard/users/:id" component={UserDetailPage} />
</IonRouterOutlet>
</IonPage>
);
};

在这里,还有几个路由被定义,用于指向应用仪表板部分内的页面。请注意,我们需要在路径中定义完整的路由,即使我们是从该 URL 到达此页面,也不能省略“/dashboard”。React Router 需要完整的路径,不支持相对路径。

🌐 Here, there are a couple more routes defined to point to pages from within the dashboard portion of the app. Note, that we need to define the whole route in the path, and we can't leave off "/dashboard" even though we arrived to this page from that URL. React Router requires full paths, and relative paths are not supported.

但是,我们可以使用 match 对象的 url 属性来提供用于渲染组件的匹配 URL,这在处理嵌套路由时很有帮助:

🌐 However, we can use the match objects url property to provide the URL that was matched to render a component, which helps when working with nested routes:

const DashboardPage: React.FC<RouteComponentProps> = ({ match }) => {
return (
<IonPage>
<IonRouterOutlet>
<Route exact path={match.url} component={UsersListPage} />
<Route path={`${match.url}/users/:id`} component={UserDetailPage} />
</IonRouterOutlet>
</IonPage>
);
};

这里,match.url 包含 "/dashboard" 的值,因为这是用于渲染 DashboardPage 的 URL。

🌐 Here, match.url contains the value of "/dashboard", since that was the URL used to render the DashboardPage.

这些路由被归入 IonRouterOutlet,我们接下来讨论它。

🌐 These routes are grouped in an IonRouterOutlet, let's discuss that next.

IonRouterOutlet

IonRouterOutlet 组件提供了一个容器,用于渲染 Ionic “页面”的路由。当一个页面处于 IonRouterOutlet 中时,该容器控制页面之间的过渡动画,并控制页面的创建和销毁,这有助于在页面之间来回切换时保持视图之间的状态。

🌐 The IonRouterOutlet component provides a container for Routes that render Ionic "pages". When a page is in an IonRouterOutlet, the container controls the transition animation between the pages as well as controls when a page is created and destroyed, which helps maintain the state between the views when switching back and forth between them.

上面的 DashboardPage 显示了一个用户列表页面和一个详情页面。在两个页面之间导航时,IonRouterOutlet 提供了适当的平台页面过渡,并保持前一个页面的状态不变,以便当用户导航回列表页面时,它渲染的状态与离开时相同。

🌐 The DashboardPage above shows a users list page and a details page. When navigating between the two pages, the IonRouterOutlet provides the appropriate platform page transition and keeps the state of the previous page intact so that when a user navigates back to the list page, it appears in the same state as when it left.

IonRouterOutlet 应该只包含 RouteRedirect。任何其他组件都应当通过 Route 的结果渲染,或者放在 IonRouterOutlet 外部。

🌐 An IonRouterOutlet should only contain Routes or Redirects. Any other component should be rendered either as a result of a Route or outside of the IonRouterOutlet.

后备路由

🌐 Fallback Route

一个常见的路由使用场景是提供一个“回退”路由,在导航到的位置不匹配任何已定义的路由时渲染该路由。

🌐 A common routing use case is to provide a "fallback" route to be rendered in the event the location navigated to does not match any of the routes defined.

我们可以通过在 IonRouterOutlet 中定义的最后一个路由中放置一个没有 path 属性的 Route 组件来定义一个备用路由。

🌐 We can define a fallback route by placing a Route component without a path property as the last route defined within an IonRouterOutlet.

仪表板页面.tsx

const DashboardPage: React.FC<RouteComponentProps> = ({ match }) => {
return (
<IonRouterOutlet>
<Route exact path={match.url} component={UsersListPage} />
<Route path={`${match.url}/users/:id`} component={UserDetailPage} />
<Route render={() => <Redirect to={match.url} />} />
</IonRouterOutlet>
);
};

在这里,我们看到如果某个位置不匹配前两个 RouteIonRouterOutlet 将把 Ionic React 应用重定向到 match.url 路径。

🌐 Here, we see that in the event a location does not match the first two Routes the IonRouterOutlet will redirect the Ionic React app to the match.url path.

你也可以提供一个组件来渲染,而不是提供重定向。

🌐 You can alternatively supply a component to render instead of providing a redirect.

const DashboardPage: React.FC<RouteComponentProps> = ({ match }) => {
return (
<IonRouterOutlet>
<Route exact path={match.url} component={UsersListPage} />
<Route path={`${match.url}/users/:id`} component={UserDetailPage} />
<Route component={NotFoundPage} />
</IonRouterOutlet>
);
};

IonPage

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

🌐 The IonPage component wraps each view in an Ionic React app and allows page transitions and stack navigation to work properly. Each view that is navigated to using the router must include an IonPage component.

IonPage 对于正确的样式设置也是必需的。它提供了一个弹性容器,确保页面内容,例如 IonContent,具有适当的尺寸,并且不会与其他 UI 元素(如 IonTabBar)重叠。

import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
import React from 'react';

const Home: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Home</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">Hello World</IonContent>
</IonPage>
);
};
export default Home;

🌐 Navigation

在 Ionic React 应用中,将路由导向不同视图时有几种可用选项。这里,UsersListPage 使用 IonItemrouterLink 属性来指定当项目被点击/轻触时要导航的路由:

🌐 There are several options available when routing to different views in an Ionic React app. Here, the UsersListPage uses IonItem's routerLink prop to specify the route to go to when the item is tapped/clicked:

用户列表页面.tsx

const UsersListPage: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Users</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonList>
<IonItem routerLink="/dashboard/users/1">
<IonLabel>User 1</IonLabel>
</IonItem>
<IonItem routerLink="/dashboard/users/2">
<IonLabel>User 2</IonLabel>
</IonItem>
</IonList>
</IonContent>
</IonPage>
);
};

其他具有 routerLink 属性的组件有 IonButtonIonCardIonRouterLinkIonFabButtonIonItemOption

🌐 Other components that have the routerLink prop are IonButton, IonCard, IonRouterLink, IonFabButton, and IonItemOption.

这些组件中的每一个也都有一个 routerDirection 属性,用于显式设置要使用的页面过渡类型("forward""back""root")。

🌐 Each of these components also have a routerDirection prop to explicitly set the type of page transition to use ("forward", "back", or "root").

除了具有 routerLink 属性的这些组件之外,你还可以使用 React Router 的 Link 组件在视图之间导航:

🌐 Outside of these components that have the routerLink prop, you can also use React Routers Link component to navigate between views:

<Link to="/dashboard/users/1">User 1</Link>

我们建议在可能的情况下尽量使用上述方法之一进行路由。这些方法的优点是它们都会生成一个锚点(<a>)标签,这对整体应用的可访问性是合适的。

🌐 We recommend using one of the above methods whenever possible for routing. The advantage to these approaches is that they both render an anchor (<a>)tag, which is suitable for overall app accessibility.

一种用于导航的编程选项是使用 React Router 通过路由渲染的组件提供的 history 属性。

🌐 A programmatic option for navigation is using the history prop that React Router provides to the components it renders via routes.

<IonButton
onClick={(e) => {
e.preventDefault();
history.push('/dashboard/users/1');
}}
>
Go to User 1
</IonButton>
note

history 是一个属性。

🌐 Navigating using history.go

React Router 使用 history 包,该包具有 history.go 方法,允许开发者在应用历史记录中前进或后退。让我们来看一个示例。

🌐 React Router uses the history package which has a history.go method that allows developers to move forward or backward through the application history. Let's take a look at an example.

假设你有以下应用历史记录:

🌐 Say you have the following application history:

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

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

🌐 If you were to call router.go(-2) on /pageC, you would be brought back to /pageA. If you then called router.go(2), you would be brought to /pageC.

目前在 Ionic React 中不支持使用 history.go()。有兴趣看到在 Ionic React 中添加对它的支持吗?在 GitHub 上告诉我们

🌐 Using history.go() in Ionic React is not supported at the moment. Interested in seeing support for this get added to Ionic React? Let us know on GitHub!

网址参数

🌐 URL Parameters

在仪表板页面中定义的第二条路由具有定义的 URL 参数(路径中的 ":id" 部分)。URL 参数是 path 的动态部分,当用户导航到诸如 "/dashboard/users/1" 的 URL 时,"1" 会被保存到名为 "id" 的参数中,该参数可以在路由渲染的组件中访问。让我们看看这是如何实现的。

🌐 The second route defined in the Dashboard Page has a URL parameter defined (the ":id" portion in the path). URL parameters are dynamic portions of the path, and when the user navigates to a URL such as "/dashboard/users/1", the "1" is saved to a parameter named "id", which can be accessed in the component the route renders. Let's see how that's done.

用户详情页面.tsx

interface UserDetailPageProps
extends RouteComponentProps<{
id: string;
}> {}

const UserDetailPage: React.FC<UserDetailPageProps> = ({ match }) => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>User Detail</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>User {match.params.id}</IonContent>
</IonPage>
);
};

match 属性包含有关匹配路由的信息,包括 URL 参数。我们在这里获取 id 参数并将其显示在屏幕上。

🌐 The match prop contains information about the matched route, including the URL params. We obtain the id param here and display it on the screen.

注意我们如何使用 TypeScript 接口来对 props 对象进行强类型定义。接口为组件内部提供了类型安全和代码补全。

🌐 Note how we use a TypeScript interface to strongly type the props object. The interface gives us type safety and code completion inside of the component.

线性路由与非线性路由

🌐 Linear Routing versus Non-Linear Routing

线性路由

🌐 Linear Routing

如果你已经构建了一个使用路由的网页应用,你可能以前使用过线性路由。线性路由意味着你可以通过推入和弹出页面在应用历史记录中前进或后退。

🌐 If you have built a web app that uses routing, you likely have used linear routing before. Linear routing means that you can move forward or backward through the application history by pushing and popping pages.

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

🌐 The following is an example of linear routing in a mobile app:

本例中的应用历史记录具有以下路径:

🌐 The application history in this example has the following path:

Accessibility --> VoiceOver --> Speech

当我们按下返回按钮时,我们遵循相同的路由路径,只是方向相反。线性路由的好处在于它允许简单且可预测的路由行为。

🌐 When we press the back button, we follow that same routing path except in reverse. Linear routing is helpful in that it allows for simple and predictable routing behaviors.

线性路由的缺点是它不允许复杂的用户体验,例如选项卡视图。这就是非线性路由的用武之地。

🌐 The downside of linear routing is that it does not allow for complex user experiences such as tab views. This is where non-linear routing comes into play.

非线性路由

🌐 Non-Linear Routing

对于许多学习使用 Ionic 构建移动应用的 Web 开发者来说,非线性路由可能是一个新概念。

🌐 Non-linear routing is a concept that may be new to many web developers learning to build mobile apps with Ionic.

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

🌐 Non-linear routing means that the view that the user should go back to is not necessarily the previous view that was displayed on the screen.

以下是非线性路由的示例:

🌐 The following is an example of non-linear routing:

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

🌐 In the example above, we start on the Originals tab. Tapping a card brings us to the Ted Lasso view within the Originals tab.

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

🌐 From here, we switch to the Search tab. Then, we tap the Originals tab again and are brought back to the Ted Lasso view. At this point, we have started using non-linear routing.

为什么这是非线性路由?我们之前所在的视图是 Search 视图。然而,在 Ted Lasso 视图上按返回按钮应该会带我们回到根 Originals 视图。这发生的原因是移动应用中的每个标签页都被视为自己的栈。使用标签页 部分对此有更详细的说明。

🌐 Why is this non-linear routing? The previous view we were on was the Search view. However, pressing the back button on the Ted Lasso view should bring us back to the root Originals view. This happens because each tab in a mobile app is treated as its own stack. The Working with Tabs sections goes over this in more detail.

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

🌐 If tapping the back button simply called history.go(-1) from the Ted Lasso view, we would be brought back to the Search view which is not correct.

非线性路由允许实现线性路由无法处理的复杂用户流程。然而,某些线性路由 API(如 history.go())无法在这种非线性环境中使用。这意味着在使用标签页或嵌套出口时,不应使用 history.go()

🌐 Non-linear routing allows for sophisticated user flows that linear routing cannot handle. However, certain linear routing APIs such as history.go() cannot be used in this non-linear environment. This means that history.go() should not be used when using tabs or nested outlets.

我应该选择哪一个?

🌐 Which one should I choose?

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

🌐 We recommend keeping your application as simple as possible until you need to add non-linear routing. Non-linear routing is very powerful, but it also adds a considerable amount of complexity to mobile applications.

非线性路由的两种最常见的用法是用于选项卡和嵌套的 IonRouterOutlets。我们建议仅在你的应用符合选项卡或嵌套路由出口的使用场景时使用非线性路由。

🌐 The two most common uses of non-linear routing is with tabs and nested IonRouterOutlets. We recommend only using non-linear routing if your application meets the tabs or nested router outlet use cases.

有关标签的更多信息,请参阅 使用标签

🌐 For more on tabs, please see Working with Tabs.

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

🌐 For more on nested router outlets, please see Nested Routes.

共享 URL 与嵌套路由

🌐 Shared URLs versus Nested Routes

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

🌐 A common point of confusion when setting up routing is deciding between shared URLs or nested routes. This part of the guide will explain both and help you decide which one to use.

共享网址

🌐 Shared URLs

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

🌐 Shared URLs is a route configuration where routes have pieces of the URL in common. The following is an example of a shared URL configuration:

const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/dashboard" exact={true}>
<DashboardMainPage />
</Route>
<Route path="/dashboard/stats" exact={true}>
<DashboardStatsPage />
</Route>
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);

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

🌐 The above routes are considered "shared" because they reuse the dashboard piece of the URL.

嵌套路由

🌐 Nested Routes

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

🌐 Nested Routes is a route configuration where routes are listed as children of other routes. The following is an example of a nested route configuration:

const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/dashboard/:id">
<DashboardRouterOutlet />
</Route>
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);

const DashboardRouterOutlet: React.FC = () => (
<IonRouterOutlet>
<Route path="/dashboard" exact={true}>
<DashboardMainPage />
</Route>
<Route path="/dashboard/stats" exact={true}>
<DashboardStatsPage />
</Route>
</IonRouterOutlet>
);

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

🌐 The above routes are nested because they are in the children array of the parent route. Notice that the parent route renders the DashboardRouterOutlet component. When you nest routes, you need to render another instance of IonRouterOutlet.

我应该选择哪一个?

🌐 Which one should I choose?

共享的 URL 非常适合在希望从页面 A 过渡到页面 B 的同时保留两个页面之间关系的情况。在我们之前的示例中,/dashboard 页面上的一个按钮可以过渡到 /dashboard/stats 页面。两个页面之间的关系得以保留,因为 a) 页面过渡 和 b) URL。

🌐 Shared URLs are great when you want to transition from page A to page B while preserving the relationship between the two pages in the URL. In our previous example, a button on the /dashboard page could transition to the /dashboard/stats page. The relationship between the two pages is preserved because of a) the page transition and b) the url.

当你想在 outlet A 中渲染内容的同时,也在嵌套的 outlet B 中渲染子内容时,应使用嵌套路由。你最常遇到的用例是标签页。当你加载一个 tabs Ionic 入门应用时,你会看到 IonTabBarIonTabs 组件在第一个 IonRouterOutlet 中渲染。IonTabs 组件渲染另一个 IonRouterOutlet,它负责渲染每个标签页的内容。

🌐 Nested routes should be used when you want to render content in outlet A while also rendering sub-content inside of a nested outlet B. The most common use case you will run into is tabs. When you load up a tabs Ionic starter application, you will see IonTabBar and IonTabs components rendered in the first IonRouterOutlet. The IonTabs component renders another IonRouterOutlet which is responsible for rendering the contents of each tab.

在移动应用中,嵌套路由合理的用例非常少。如果不确定,请使用共享的 URL 路由配置。我们强烈建议除了标签页之外的上下文不要使用嵌套路由,因为它可能会迅速使你的应用导航变得混乱。

🌐 There are very few use cases in which nested routes make sense in mobile applications. When in doubt, use the shared URL route configuration. We strongly caution against using nested routing in contexts other than tabs as it can quickly make navigating your app confusing.

使用选项卡

🌐 Working with Tabs

在使用标签页时,Ionic 需要一种方法来知道哪个视图属于哪个标签页。IonTabs 组件在这里很有用,但让我们看看这类路由设置是怎样的:

🌐 When working with tabs, Ionic needs a way to know which view belongs to which tab. The IonTabs component comes in handy here, but let's look at what the routing setup for this looks like:

<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/tabs" render={() => <Tabs />} />
<Route exact path="/">
<Redirect to="/tabs" />
</Route>
</IonRouterOutlet>
</IonReactRouter>
</IonApp>

在这里,我们的 tabs 路径加载一个 Tabs 组件。我们在这个组件内将每个标签作为路由对象提供。在这个示例中,我们将路径称为 tabs,但这可以自定义。

🌐 Here, our tabs path loads a Tabs component. We provide each tab as a route object inside of this component. In this example, we call the path tabs, but this can be customized.

让我们先来看看我们的 Tabs 组件:

🌐 Let's start by taking a look at our Tabs component:

import { Redirect, Route } from 'react-router-dom';
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { ellipse, square, triangle } from 'ionicons/icons';
import Tab1 from './pages/Tab1';
import Tab2 from './pages/Tab2';
import Tab3 from './pages/Tab3';

const Tabs: React.FC = () => (
<IonTabs>
<IonRouterOutlet>
<Redirect exact path="/tabs" to="/tabs/tab1" />
<Route exact path="/tabs/tab1">
<Tab1 />
</Route>
<Route exact path="/tabs/tab2">
<Tab2 />
</Route>
<Route path="/tabs/tab3">
<Tab3 />
</Route>
<Route exact path="/tabs">
<Redirect to="/tabs/tab1" />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" href="/tabs/tab1">
<IonIcon icon={triangle} />
<IonLabel>Tab 1</IonLabel>
</IonTabButton>
<IonTabButton tab="tab2" href="/tabs/tab2">
<IonIcon icon={ellipse} />
<IonLabel>Tab 2</IonLabel>
</IonTabButton>
<IonTabButton tab="tab3" href="/tabs/tab3">
<IonIcon icon={square} />
<IonLabel>Tab 3</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
);

export default Tabs;

如果你以前使用过 Ionic 框架,这应该会感觉很熟悉。我们创建一个 IonTabs 组件并提供一个 IonTabBarIonTabBar 提供 IonTabButton 组件,每个组件都有一个 tab 属性,该属性与路由配置中的相应标签相关联。我们还提供一个 IonRouterOutlet 来为 IonTabs 提供一个出口,用于渲染不同的标签视图。

🌐 If you have worked with Ionic Framework before, this should feel familiar. We create an IonTabs component and provide an IonTabBar. The IonTabBar provides IonTabButton components, each with a tab property that is associated with its corresponding tab in the router config. We also provide an IonRouterOutlet to give IonTabs an outlet to render the different tab views in.

tip

IonTabs 为你渲染一个 IonPage,因此你无需在此手动添加 IonPage

Ionic 中的选项卡工作原理

🌐 How Tabs in Ionic Work

Ionic 中的每个标签都被视为一个独立的导航栈。这意味着如果你的应用中有三个标签,每个标签都有其自己的导航栈。在每个栈中,你可以向前导航(推入一个视图)和向后导航(弹出一个视图)。

🌐 Each tab in Ionic is treated as an individual navigation stack. This means if you have three tabs in your application, each tab has its own navigation stack. Within each stack you can navigate forwards (push a view) and backwards (pop a view).

需要注意这种行为,因为它不同于大多数其他基于网页的 UI 库中发现的标签实现。其他库通常将标签管理为一个单一的历史堆栈。

🌐 This behavior is important to note as it is different than most tab implementations that are found in other web based UI libraries. Other libraries typically manage tabs as one single history stack.

由于 Ionic 专注于帮助开发者构建移动应用,因此 Ionic 中的标签页设计尽可能地与原生移动标签页相匹配。因此,Ionic 的标签页可能会存在某些行为,与你在其他 UI 库中见过的标签页实现有所不同。请继续阅读以了解更多关于这些差异的信息。

🌐 Since Ionic is focused on helping developers build mobile apps, the tabs in Ionic are designed to match native mobile tabs as closely as possible. As a result, there may be certain behaviors in Ionic's tabs that differ from tabs implementations you have seen in other UI libraries. Read on to learn more about some of these differences.

选项卡中的子路由

🌐 Child Routes within Tabs

在向标签页添加额外路由时,你应该将它们写为与父标签页同级的路由,并将父标签页作为路径前缀。下面的示例将 /tabs/tab1/view 路由定义为 /tabs/tab1 路由的同级路由。由于这个新路由具有 tab1 前缀,它将渲染在 Tabs 组件内,并且在 IonTabBar 中标签 1 仍将被选中。

🌐 When adding additional routes to tabs you should write them as sibling routes with the parent tab as the path prefix. The example below defines the /tabs/tab1/view route as a sibling of the /tabs/tab1 route. Since this new route has the tab1 prefix, it will be rendered inside of the Tabs component, and Tab 1 will still be selected in the IonTabBar.

<IonTabs>
<IonRouterOutlet>
<Redirect exact path="/tabs" to="/tabs/tab1" />
<Route exact path="/tabs/tab1">
<Tab1 />
</Route>
<Route exact path="/tabs/tab1/view">
<Tab1View />
</Route>
<Route exact path="/tabs/tab2">
<Tab2 />
</Route>
<Route path="/tabs/tab3">
<Tab3 />
</Route>
<Route exact path="/tabs">
<Redirect to="/tabs/tab1" />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" href="/tabs/tab1">
<IonIcon icon={triangle} />
<IonLabel>Tab 1</IonLabel>
</IonTabButton>
<IonTabButton tab="tab2" href="/tabs/tab2">
<IonIcon icon={ellipse} />
<IonLabel>Tab 2</IonLabel>
</IonTabButton>
<IonTabButton tab="tab3" href="/tabs/tab3">
<IonIcon icon={square} />
<IonLabel>Tab 3</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>

在选项卡之间切换

🌐 Switching Between Tabs

由于每个标签页都有自己的导航堆栈,因此需要注意,这些导航堆栈永远不应相互交互。这意味着在标签页1中不应有将用户引导到标签页2的按钮。换句话说,标签页的切换应仅通过用户点击标签栏中的标签按钮来进行。

🌐 Since each tab is its own navigation stack, it is important to note that these navigation stacks should never interact. This means that there should never be a button in Tab 1 that routes a user to Tab 2. In other words, tabs should only be changed by the user tapping a tab button in the tab bar.

在实践中的一个很好的例子是 iOS App Store 和 Google Play Store 移动应用。这些应用都提供了标签界面,但没有一个会将用户跨标签引导。例如,iOS App Store 应用中的“游戏”标签从不将用户引导到“搜索”标签,反之亦然。

🌐 A good example of this in practice is the iOS App Store and Google Play Store mobile applications. These apps both provide tabbed interfaces, but neither one ever routes the user across tabs. For example, the "Games" tab in the iOS App Store app never directs users to the "Search" tab and vice versa.

让我们看一下使用选项卡时常犯的几个常见错误。

🌐 Let's take a look at a couple common mistakes that are made with tabs.

一个被多个标签引用的设置标签

一种常见的做法是将设置视图创建为其自己的标签页。如果开发者需要展示多个嵌套的设置菜单,这是非常好的。然而,其他标签页绝不应该尝试跳转到设置标签页。如上所述,设置标签页唯一应被激活的方式是用户点击相应的标签按钮。

🌐 A common practice is to create a Settings view as its own tab. This is great if developers need to present several nested settings menus. However, other tabs should never try to route to the Settings tab. As we mentioned above, the only way that the Settings tab should be activated is by a user tapping the appropriate tab button.

如果你发现你的标签需要引用“设置”标签,我们建议通过使用 ion-modal 将“设置”视图设为模态。这是在 iOS App Store 应用中常见的做法。通过这种方式,任何标签都可以渲染模态,而不会破坏每个标签各自成为独立堆栈的移动标签模式。

🌐 If you find that your tabs need to reference the Settings tab, we recommend making the Settings view a modal by using ion-modal. This is a practice found in the iOS App Store app. With this approach, any tab can present the modal without breaking the mobile tabs pattern of each tab being its own stack.

下面的示例显示了 iOS 应用商店应用如何处理从多个标签页渲染“账户”视图。通过以模态方式渲染“账户”视图,应用可以在移动标签页的最佳实践范围内在多个标签页中显示相同的视图。

🌐 The example below shows how the iOS App Store app handles presenting an "Account" view from multiple tabs. By presenting the "Account" view in a modal, the app can work within the mobile tabs best practices to show the same view across multiple tabs.

在标签页之间重用视图

另一种常见做法是将相同的视图渲染在多个标签页中。开发者经常尝试通过将视图包含在单个标签页中,而其他标签页路由到该标签页来实现这一点。如上文所述,这会破坏移动标签页的模式,应当避免。

🌐 Another common practice is to present the same view in multiple tabs. Developers often try to do this by having the view contained in a single tab, with other tabs routing to that tab. As we mentioned above, this breaks the mobile tabs pattern and should be avoided.

相反,我们建议在每个标签中设置引用相同组件的路由。这是在像 Spotify 这样的流行应用中使用的做法。例如,你可以从“首页”、“搜索”和“你的资料库”标签访问专辑或播客。当访问专辑或播客时,用户仍停留在该标签中。应用通过为每个标签创建路由并在代码库中共享一个通用组件来实现这一点。

🌐 Instead, we recommend having routes in each tab that reference the same component. This is a practice done in popular apps like Spotify. For example, you can access an album or podcast from the "Home", "Search", and "Your Library" tabs. When accessing the album or podcast, users stay within that tab. The app does this by creating routes per tab and sharing a common component in the codebase.

下面的示例展示了 Spotify 应用如何重用相同的专辑组件在多个标签中显示内容。请注意,每个截图显示的是相同的专辑,但来自不同的标签。

🌐 The example below shows how the Spotify app reuses the same album component to show content in multiple tabs. Notice that each screenshot shows the same album but from a different tab.

首页标签搜索标签

实例

🌐 Live Example

如果你希望亲自动手实践上面描述的概念和代码,请查看我们在 StackBlitz 上的上述主题的实时示例

🌐 If you would prefer to get hands on with the concepts and code described above, please checkout our live example of the topics above on StackBlitz.

选项卡视图中的 IonRouterOutlet

🌐 IonRouterOutlet in a Tabs View

在使用标签页视图时,Ionic React 需要一种方法来确定哪些视图属于哪些标签页。我们通过利用提供给 Route 的路径是正则表达式这一事实来实现这一点。

🌐 When working in a tabs view, Ionic React needs a way to determine what views belong to which tabs. We accomplish this by taking advantage of the fact that the paths provided to a Route are regular expressions.

虽然语法看起来有点奇怪,但一旦理解了它就相当简单了。

🌐 While the syntax looks a bit strange, it is reasonably straightforward once you understand it.

例如,具有两个选项卡(会话和演讲者)的视图的路由可以设置如下:

🌐 For example, the routes for a view with two tabs (sessions and speakers) can be set up as such:

<IonRouterOutlet>
<Route path="/:tab(sessions)" component={SessionsPage} exact={true} />
<Route path="/:tab(sessions)/:id" component={SessionDetail} />
<Route path="/:tab(speakers)" component={SpeakerList} exact={true} />
</IonRouterOutlet>

如果导航的 URL 是 "/sessions",它将匹配第一个路由,并在传递给 SessionsPage 的结果 match 对象中添加一个名为 "tab"、值为 "sessions" 的 URL 参数。

🌐 If the navigated URL were "/sessions", it would match the first route and add a URL parameter named "tab" with the value of "sessions" to the resulting match object passed into SessionsPage.

当用户导航到会话详情页面(例如 "/sessions/1")时,第二个路由会添加一个名为 "tab" 的 URL 参数,值为 "sessions"。当 IonRouterOutlet 看到两个页面都在同一个 "sessions" 标签下时,它会提供一个动画页面过渡到新视图。如果用户导航到一个新标签(在此情况下为 "speakers"),IonRouterOutlet 会知道不提供动画。

🌐 When a user navigates to a session detail page ("/sessions/1" for instance), the second route adds a URL parameter named "tab" with a value of "sessions". When IonRouterOutlet sees that both pages are in the same "sessions" tab, it provides an animated page transition to the new view. If a user navigates to a new tab ("speakers" in this case), IonRouterOutlet knows not to provide the animation.

IonRouterOutlet 中的开关

🌐 Switches in IonRouterOutlet

由于 IonRouterOutlet 接管了决定渲染哪些路由的工作,所以在 IonRouterOutlet 内使用 React Router 的 Switch 是没有效果的。当在 IonRouterOutlet 外使用时,Switch 仍然可以按预期工作。

🌐 Since IonRouterOutlet takes over the job in determining which routes get rendered, using a Switch from React Router has no effect when used inside of an IonRouterOutlet. Switches still function as expected when used outside an IonRouterOutlet.

实用工具

🌐 Utilities

useIonRouter

useIonRouter 钩子可用于在 Ionic React 中对路由进行更直接的控制。它允许你在调用 React Router 之前向 Ionic 传递额外的元数据,例如自定义动画。

🌐 The useIonRouter hook can be used for more direct control over routing in Ionic React. It allows you to pass additional metadata to Ionic, such as a custom animation, before calling React Router.

useIonRouter 钩子返回一个 UseIonRouterResult,它具有多个用于路由的便捷方法:

🌐 The useIonRouter hook returns a UseIonRouterResult which has several convenience methods for routing:

type UseIonRouterResult = {
/**
* Navigates to a new pathname
* @param pathname - The path to navigate to
* @param routerDirection - Optional - The RouterDirection to use for transition purposes, defaults to 'forward'
* @param routeAction - Optional - The RouteAction to use for history purposes, defaults to 'push'
* @param routerOptions - Optional - Any additional parameters to pass to the router
* @param animationBuilder - Optional - A custom transition animation to use
*/
push(
pathname: string,
routerDirection?: RouterDirection,
routeAction?: RouteAction,
routerOptions?: RouterOptions,
animationBuilder?: AnimationBuilder
): void;
/**
* Navigates backwards in history, using the IonRouter to determine history
* @param animationBuilder - Optional - A custom transition animation to use
*/
goBack(animationBuilder?: AnimationBuilder): void;
/**
* Determines if there are any additional routes in the the Router's history. However, routing is not prevented if the browser's history has more entries. Returns true if more entries exist, false if not.
*/
canGoBack(): boolean;
/**
* Information about the current route.
*/
routeInfo: RouteInfo;
};

以下示例显示了如何使用 useIonRouter

🌐 The following example shows how to use useIonRouter:

import { useIonRouter } from '@ionic/react';

const MyComponent: React.FC = () => {
const router = useIonRouter();
const goToPage = () => {
router.push('/my-page', 'root', 'replace');
};

...
}

更多信息

🌐 More Information

有关在 React 中使用 Ionic 底层实现的 React Router 进行路由的更多信息,请查看他们的文档:https://v5.reactrouter.com/web

🌐 For more info on routing in React using the React Router implementation that Ionic uses under the hood, check out their docs at https://v5.reactrouter.com/web.

来自社区

🌐 From the Community

Ionic 4 和 React:导航 - Paul Halliday