跳到主要内容

将 React Navigation 5 与 UI Kitten 结合使用

·12 分钟阅读
Artur Yorsh
Artur Yorsh
UI Kitten 团队

这是一篇由 UI Kitten 团队撰写的客座文章。如果您喜欢本指南,请查看 UI Kitten 以获取更多信息!在这篇博客文章中,我们将展示一个关于将 React Navigation 5 与 UI Kitten 结合使用的分步指南。

简介

新的 React Navigation 带来了一些重大改进,例如使用 gesture-handlerreanimated提高动画性能。更重要的是,它已迁移到 TypeScript,通过类型检查等方式来提高代码库的质量。但最大的更新是迁移到基于组件的 API。

Eva Design System 是一个可定制的设计系统,易于适应您的品牌。它提供了移动和 Web 组件库,并允许企业快速创建美观独特的品牌主题。Eva Design System 的 React Native 实现包括 UI Kitten,这是一个用于构建现代跨平台移动应用程序的 React Native 框架。

UI Kitten 团队开始积极使用 React Navigation alpha,我们很自豪地宣布与新的 React Navigation API 完全兼容。在本指南中,我们不会考虑如何实现所有样板代码,例如身份验证屏幕。相反,我们将学习如何使用 Drawer、Bottom Tabs、Top Tabs 和 Stack 导航器在屏幕之间导航,以构建一个 TODO 应用程序。此外,我们将演示如何将 React Navigation 与 UI Kitten 组件一起使用。

React Navigation with UI Kitten Overview

概述

React Navigation 5 无非是简化应用程序中的导航结构。

import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

export const AuthNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='Sign In' component={SignInScreen}/>
<Stack.Screen name='Sign Up' component={SignUpScreen}/>
</Stack.Navigator>
);

要创建导航器,您需要从您选择的导航器包中导入 createXNavigator 函数,并使用它返回的值中的 NavigatorScreen 组件。

与之前的 React Navigation 版本不同,导航器内部使用的所有屏幕都作为子元素传递,并将其包装到 Screen 组件中。如果您需要设置额外的导航器配置(如标题的配置),您可以直接将相应的 props 传递给 Navigator 组件。

开始使用

从 GitHub 克隆项目。它包含初始设置所需的所有源代码。

git clone https://github.com/artyorsh/react-navigation-ex-demo

步骤 1. 身份验证流程

假设您的应用程序用户在进入主屏幕之前需要进行身份验证,我们将需要创建身份验证和主页导航器。然后,我们将把它与简单的堆栈导航结合起来,并根据用户身份验证状态选择初始屏幕。

打开 ./src/navigation/auth.navigator.tsx` 文件并粘贴以下代码

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AppRoute } from './app-routes';
import { SignInScreen, SignUpScreen, ResetPasswordScreen } from '../scenes/auth';

const Stack = createStackNavigator();

export const AuthNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name={AppRoute.SIGN_IN} component={SignInScreen}/>
<Stack.Screen name={AppRoute.SIGN_UP} component={SignUpScreen}/>
<Stack.Screen name={AppRoute.RESET_PASSWORD} component={ResetPasswordScreen}/>
</Stack.Navigator>
);

在本例中,我们使用 createStackNavigator 函数在“登录”、“注册”和“重置密码”屏幕之间创建简单的堆栈导航。在 Stack Navigator 下,我们指的是屏幕之间的默认导航行为:iOS 上为从右向左滑动动画,Android 上为从上向下滑入动画。

./src/navigation/app.navigator.tsx 文件中,将占位符屏幕替换为 Auth Navigator。这将使身份验证屏幕成为应用程序的起始点。

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthNavigator } from './auth.navigator';
import { AppRoute } from './app-routes';

const Stack = createStackNavigator();

export const AppNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name={AppRoute.AUTH} component={AuthNavigator}/>
</Stack.Navigator>
);
UI Kitten with Stack Navigator

步骤 2. 顶部标签页

假设我们的应用程序既有进行中的任务,也有已完成的任务。因此,您应该将它们分开以避免混乱。在这里,您可以在主屏幕上使用两个标签页来实现这一点。为此,我们需要有三个屏幕:两个用于标签页,一个用于管理标签页之间导航的主屏幕。与 Stack Navigator 组件不同,Top Tabs Navigator 有一个特殊的 prop 用于控制标签页之间导航的组件 - tabBar。我们将使用它来配置带有 UI Kitten 组件的标签栏。

打开 ./src/navigation/todo.navigator.tsx` 文件并粘贴以下代码

import React from 'react';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { AppRoute } from './app-routes';
import { TodoTabBar, TodoInProgressScreen, TodoDoneScreen } from '../scenes/todo';
import { DoneAllIcon, GridIcon } from '../assets/icons';

const TopTab = createMaterialTopTabNavigator();

export const TodoNavigator = (): React.ReactElement => (
<TopTab.Navigator tabBar={props => <TodoTabBar {...props} />}>
<TopTab.Screen
name={AppRoute.TODO_IN_PROGRESS}
component={TodoInProgressScreen}
options={{ title: 'IN PROGRESS', tabBarIcon: GridIcon }}
/>
<TopTab.Screen
name={AppRoute.TODO_DONE}
component={TodoDoneScreen}
options={{ title: 'DONE', tabBarIcon: DoneAllIcon }}
/>
</TopTab.Navigator>
);

上面的代码将使您能够通过手势在 进行中 屏幕和 已完成 屏幕之间导航,但不会设置标签栏。打开 ./src/scenes/todo/todo-tab-bar.component.tsx 文件并粘贴以下代码

import React from 'react';
import { TabBar, Tab, Divider, TabElement } from '@ui-kitten/components';
import { SafeAreaLayout, SaveAreaInset, SafeAreaLayoutElement } from '../../components/safe-area-layout.component';
import { Toolbar } from '../../components/toolbar.component';

export const TodoTabBar = (props): SafeAreaLayoutElement => {

const onTabSelect = (index: number): void => {
const selectedTabRoute: string = props.state.routeNames[index];
props.navigation.navigate(selectedTabRoute);
};

const createNavigationTabForRoute = (route): TabElement => {
const { options } = props.descriptors[route.key];
return (
<Tab
key={route.key}
title={options.title}
icon={options.tabBarIcon}
/>
);
};

return (
<SafeAreaLayout insets={SaveAreaInset.TOP}>
<Toolbar title='React Navigation Ex 🐱'/>
<TabBar selectedIndex={props.state.index} onSelect={onTabSelect}>
{props.state.routes.map(createNavigationTabForRoute)}
</TabBar>
<Divider/>
</SafeAreaLayout>
);
};

使用上面的代码,我们渲染了 TabBar 组件,其中包含两个标签页:TodoNavigator 内的每个屏幕一个标签页。然后,我们使用 React Navigation 状态来传递 selectedIndexonSelect props 以在屏幕之间导航。因此,当用户点击其中一个标签页时,TabBar 组件会调用 onTabSelect 函数,而这正是我们需要导航到相应路由的地方。

最后,打开 app.navigator.tsx 文件并将 TodoNavigator 添加为主屏幕。现在,您可以在登录后使用 todo 标签页导航主屏幕。

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthNavigator } from './auth.navigator';
import { TodoNavigator } from './todo.navigator';
import { AppRoute } from './app-routes';

const Stack = createStackNavigator();

export const AppNavigator = (props): React.ReactElement => (
<Stack.Navigator {...props} headerMode='none'>
<Stack.Screen name={AppRoute.AUTH} component={AuthNavigator}/>
<Stack.Screen name={AppRoute.HOME} component={TodoNavigator}/>
</Stack.Navigator>
);
UI Kitten with Material Top Tabs

步骤 3. 底部标签页

有时您可能希望您的应用程序在底部包含标签页。以下是关于顶部标签页的主要语义区别:虽然它们应该代表相同类型的内容,但底部标签页可以用于显示应用程序的任何内容。这就是我们将要使用 createBottomTabNavigatorBottomNavigation 的地方。

让我们从为第二个标签页创建另一个导航器开始。第一个将用于 Todo 屏幕。打开 ./src/navigation/profile.navigator.tsx 文件并粘贴以下代码

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AppRoute } from './app-routes';
import { ProfileScreen } from '../scenes/profile';

const Stack = createStackNavigator();

export const ProfileNavigator = (): React.ReactElement => (
<Stack.Navigator headerMode='none'>
<Stack.Screen name={AppRoute.PROFILE} component={ProfileScreen}/>
</Stack.Navigator>
);

这将添加一个简单的堆栈导航器,就像我们为身份验证流程所做的那样。

现在我们需要以某种方式将 TodoNavigatorProfileNavigator 连接起来。实现就像创建顶部标签页的导航器一样简单。感谢 React Navigation,我们为此拥有完全相同的 API。打开 ./src/navigation/home.navigator.tsx 文件并粘贴以下代码

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { TodoNavigator } from './todo.navigator';
import { ProfileNavigator } from './profile.navigator';
import { AppRoute } from './app-routes';
import { HomeTabBar } from '../scenes/home';
import { LayoutIcon, PersonIcon } from '../assets/icons';

const BottomTab = createBottomTabNavigator();

export const HomeNavigator = (): React.ReactElement => (
<BottomTab.Navigator tabBar={props => <HomeTabBar {...props} />}>
<BottomTab.Screen
name={AppRoute.TODO}
component={TodoNavigator}
options={{ title: 'TODO', tabBarIcon: LayoutIcon }}
/>
<BottomTab.Screen
name={AppRoute.PROFILE}
component={ProfileNavigator}
options={{ title: 'PROFILE', tabBarIcon: PersonIcon }}
/>
</BottomTab.Navigator>
);

就像顶部标签页的情况一样,我们还需要制作一个自定义的 tabBar。打开 ./src/scenes/home/home-tab-bar.component.tsx 文件并粘贴以下代码

import React from 'react';
import { BottomNavigation, BottomNavigationTab, Divider, BottomNavigationTabElement } from '@ui-kitten/components';
import { SafeAreaLayout, SafeAreaLayoutElement, SaveAreaInset } from '../../components/safe-area-layout.component';

export const HomeTabBar = (props): SafeAreaLayoutElement => {

const onSelect = (index: number): void => {
const selectedTabRoute: string = props.state.routeNames[index];
props.navigation.navigate(selectedTabRoute);
};

const createNavigationTabForRoute = (route): BottomNavigationTabElement => {
const { options } = props.descriptors[route.key];
return (
<BottomNavigationTab
key={route.key}
title={options.title}
icon={options.tabBarIcon}
/>
);
};

return (
<SafeAreaLayout insets={SaveAreaInset.BOTTOM}>
<Divider/>
<BottomNavigation
appearance='noIndicator'
selectedIndex={props.state.index}
onSelect={onSelect}>
{props.state.routes.map(createNavigationTabForRoute)}
</BottomNavigation>
</SafeAreaLayout>
);
};

使用上面的代码,我们渲染了 BottomNavigation 组件,其中包含两个标签页:HomeNavigator 内的每个屏幕一个标签页。我们使用 React Navigation 状态来传递 selectedIndexonSelect props 以在屏幕之间导航。因此,当用户点击其中一个标签页时,BottomNavigation 组件会调用 onSelect 函数。嗯,这正是我们需要导航到相应路由的地方。

然后,打开 app.navigator.tsx 文件并将 TodoNavigator 替换为 HomeNavigator

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthNavigator } from './auth.navigator';
import { HomeNavigator } from './home.navigator';
import { AppRoute } from './app-routes';

const Stack = createStackNavigator();

export const AppNavigator = (props): React.ReactElement => (
<Stack.Navigator {...props} headerMode='none'>
<Stack.Screen name={AppRoute.AUTH} component={AuthNavigator}/>
<Stack.Screen name={AppRoute.HOME} component={HomeNavigator}/>
</Stack.Navigator>
);
UI Kitten with Bottom Tabs

步骤 4. 抽屉菜单

在本指南的最后阶段,我们将描述如何创建抽屉导航。虽然顶部和底部标签页可用于呈现主要产品功能,但抽屉菜单也可用于引导用户访问有关它的法律信息,或者只是包含注销等快捷操作。

通常,抽屉菜单在应用程序的主屏幕上可用,因此让我们将其添加到 HomeNavigator。打开 ./src/navigation/home.navigator.tsx 文件并粘贴以下代码

import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { TodoNavigator } from './todo.navigator';
import { ProfileNavigator } from './profile.navigator';
import { AppRoute } from './app-routes';
import { HomeTabBar, HomeDrawer, AboutScreen } from '../scenes/home';
import { HomeIcon, InfoIcon, LayoutIcon, PersonIcon } from '../assets/icons';

const Drawer = createDrawerNavigator();
const BottomTab = createBottomTabNavigator();

const HomeBottomNavigator = (): React.ReactElement => (
<BottomTab.Navigator tabBar={props => <HomeTabBar {...props} />}>
<BottomTab.Screen
name={AppRoute.TODO}
component={TodoNavigator}
options={{ title: 'TODO', tabBarIcon: LayoutIcon }}
/>
<BottomTab.Screen
name={AppRoute.PROFILE}
component={ProfileNavigator}
options={{ title: 'PROFILE', tabBarIcon: PersonIcon }}
/>
</BottomTab.Navigator>
);

export const HomeNavigator = (): React.ReactElement => (
<Drawer.Navigator drawerContent={props => <HomeDrawer {...props} />}>
<Drawer.Screen
name={AppRoute.HOME}
component={HomeBottomNavigator}
options={{ title: 'Home', drawerIcon: HomeIcon }}
/>
<Drawer.Screen
name={AppRoute.ABOUT}
component={AboutScreen}
options={{ title: 'About', drawerIcon: InfoIcon }}
/>
</Drawer.Navigator>
);

在本例中,我们使用 createDrawerNavigator 实现了抽屉导航器,并将其用于在主屏幕上显示。我们还添加了 AboutScreen 以演示直接从抽屉菜单导航。

就像顶部/底部标签页导航器一样,抽屉导航器也具有用于声明自定义抽屉视图的特殊属性。使用 drawerContent 属性将自定义视图传递给导航器。打开 ./src/scenes/home/home-drawer.component.tsx 文件并添加以下代码

import React from 'react';
import { Drawer, DrawerItem, DrawerElement DrawerItemElement } from '@ui-kitten/components';
import { SafeAreaLayout, SaveAreaInset } from '../../components/safe-area-layout.component';

export const HomeDrawer = (props): DrawerElement => {

const onItemSelect = (index: IndexPath): void => {
const selectedTabRoute: string = props.state.routeNames[index.row];
props.navigation.navigate(selectedTabRoute);
props.navigation.closeDrawer();
};

const createDrawerItemForRoute = (route, index: number): DrawerItemElement => {
const { options } = props.descriptors[route.key];
return (
<DrawerItem
key={index}
title={route.name}
accessoryLeft={options.drawerIcon}
/>
);
};

return (
<SafeAreaLayout insets={SaveAreaInset.TOP}>
<Drawer
data={props.state.routes.map(createNavigationItemForRoute)}
onSelect={onMenuItemSelect}
/>
</SafeAreaLayout>
);
};

由于使用了此代码,我们渲染了 Drawer 组件,其中包含两个操作:一个用于导航到法律信息屏幕,一个用于执行用户注销。然后,我们传递 data prop 以显示我们的操作,并传递 onSelect prop 来处理它。因此,当用户点击操作时,Drawer 组件会调用 onMenuItemSelect 函数,而这正是我们需要处理它的地方。`

接下来要做的是修改 Todo 标签栏,添加一个菜单图标以打开抽屉。打开 ./src/scenes/todo/todo-tab-bar.component.tsx 文件并粘贴以下代码

import React from 'react';
import { TabBar, Tab, Divider, TabElement } from '@ui-kitten/components';
import { SafeAreaLayout, SaveAreaInset, SafeAreaLayoutElement } from '../../components/safe-area-layout.component';
import { Toolbar } from '../../components/toolbar.component';
import { MenuIcon } from '../../assets/icons';

export const TodoTabBar = (props): SafeAreaLayoutElement => {

const onTabSelect = (index: number): void => {
const selectedTabRoute: string = props.state.routeNames[index];
props.navigation.navigate(selectedTabRoute);
};

const createNavigationTabForRoute = (route): TabElement => {
const { options } = props.descriptors[route.key];
return (
<Tab
key={route.key}
title={options.title}
icon={options.tabBarIcon}
/>
);
};

return (
<SafeAreaLayout insets={SaveAreaInset.TOP}>
<Toolbar
title='React Navigation Ex 🐱'
backIcon={MenuIcon}
onBackPress={props.navigation.toggleDrawer}
/>
<TabBar selectedIndex={props.state.index} onSelect={onTabSelect}>
{props.state.routes.map(createNavigationTabForRoute)}
</TabBar>
<Divider/>
</SafeAreaLayout>
);
};
UI Kitten with Drawer

TypeScript

新的 React Navigation 具有出色的 TypeScript 支持,并为导航器和自定义导航组件导出类型定义。有时您可能希望在路由之间导航时对传递的参数进行类型检查。您可能还希望在使用导航 props 时使自动完成功能生效。

让我们为 Auth 屏幕添加一些类型定义。为此,打开 ./src/navigation/auth.navigator.tsx 并粘贴以下代码

import { RouteProp } from '@react-navigation/core';
import { StackNavigationProp } from '@react-navigation/stack';
import { AppRoute } from './app-routes';

type AuthNavigatorParams = {
[AppRoute.SIGN_IN]: undefined;
[AppRoute.SIGN_UP]: undefined;
[AppRoute.RESET_PASSWORD]: undefined;
}

export interface SignInScreenProps {
navigation: StackNavigationProp<AuthNavigatorParams, AppRoute.SIGN_IN>;
route: RouteProp<AuthNavigatorParams, AppRoute.SIGN_IN>;
}

export interface SignUpScreenProps {
navigation: StackNavigationProp<AuthNavigatorParams, AppRoute.SIGN_UP>;
route: RouteProp<AuthNavigatorParams, AppRoute.SIGN_UP>;
}

export interface ResetPasswordScreenProps {
navigation: StackNavigationProp<AuthNavigatorParams, AppRoute.RESET_PASSWORD>;
route: RouteProp<AuthNavigatorParams, AppRoute.RESET_PASSWORD>;
}

现在,您可以通过添加类型来修改 Auth 屏幕 props 的 props,以使您的自动完成和 IntelliSense 工作。对于更复杂的示例,请考虑阅读 类型检查 文档或查看 完整的演示应用程序源代码

UI Kitten & TypeScript

通过以下链接,您可以找到有关 UI Kitten 和 React Navigation 5 的大量有用信息。演示应用程序可能包含更复杂的示例。此外,通过参考 React Navigation 团队构建的应用程序,您也可以找到大量有用的示例。