开篇:从"一个屏幕"到"一组屏幕"

前面四课,所有代码都写在一个组件里。状态、副作用、列表、组件组合,都在同一个屏幕内完成。

但真实的 App 不可能只有一个页面。电商应用有首页、商品列表、商品详情、购物车、个人中心;社交应用有动态流、消息列表、聊天窗口、个人主页。用户需要在多个页面之间跳转、返回、传递数据。

这引出一个新问题:

如何管理多个页面,以及页面之间的跳转?

这就是导航要解决的问题。

React Native 没有内置导航功能。社区中最主流的方案是 React Navigation。它提供了路由逻辑和移动端常见的 UI 模式,支持 iOS、Android 和 Web。

这一课围绕三种导航器和一条主线展开:

// 概念表达
Stack  -> 一摞卡片,有去有回                                           // 顺序流程
Tab    -> 一排标签,平级切换                                           // 模块切换
Drawer -> 侧边菜单,全局入口                                           // 全局导航

一、导航的基本概念

1.1 移动端的三种导航模式

Web 用链接和 URL 切换页面,移动端则发展出自己的模式:

  • 堆栈式:像一摞卡片,新页面盖在旧页面上,返回时抽走最上面那张;
  • 标签式:底部或顶部有一排标签,点击切换不同模块;
  • 抽屉式:从屏幕边缘滑出一个菜单,选择要去的页面。

React Navigation 为每种模式提供了对应的导航器。理解这三种模式,就理解了移动端导航的骨架。

1.2 安装

在 Expo 项目中安装 React Navigation 核心包:

# Bash 终端命令
npx expo install @react-navigation/native react-native-screens react-native-safe-area-context  # 核心包和依赖

再安装需要的导航器:

# Bash 终端命令
npx expo install @react-navigation/native-stack                    # 堆栈导航器
npx expo install @react-navigation/bottom-tabs                      # 底部标签导航器
npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated  # 抽屉导航器

@react-navigation/native 是核心包,react-native-screens 和 react-native-safe-area-context 是大多数导航器需要的依赖。用到哪个导航器,就装哪个。

1.3 基本结构

React Navigation 的核心结构:

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器创建函数

const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器,管理导航状态
      <Stack.Navigator>                                             // 堆栈导航器,定义页面集合
        <Stack.Screen name="Home" component={HomeScreen} />        // 注册 Home 页面
        <Stack.Screen name="Details" component={DetailsScreen} />  // 注册 Details 页面
      </Stack.Navigator>                                            // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

三个关键部分,可以这样理解:

  • NavigationContainer:总控室,管理导航状态;
  • Stack.Navigator:路线图,定义有哪些页面以及它们的关系;
  • Stack.Screen:路线上的一个站点,name 是路由名称,component 是页面组件。

你只需要声明有哪些页面,不需要指挥怎么切换。 这就是"视图 = 函数(状态)"在导航层面的体现——导航状态变了,导航器自动显示对应页面。


二、Stack Navigator:堆栈导航

2.1 什么是堆栈导航

Stack Navigator 模拟"一摞卡片":

  • 打开新页面时,新页面从右侧滑入,盖在旧页面上;
  • 按返回键或返回按钮时,最上面的页面滑出,回到上一个页面;
  • 页面按打开顺序排列在堆栈中。

适合"列表 → 详情 → 详情详情"这样的顺序流程。

2.2 创建第一个堆栈导航

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器
import { View, Text, Button, StyleSheet } from 'react-native';      // 引入需要的组件

function HomeScreen({ navigation }) {                               // 定义 Home 页面,接收 navigation
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>首页</Text>                        // 页面标题
      <Button                                                       // 按钮
        title="查看详情"                                            // 按钮文字
        onPress={() => navigation.navigate('Details')}              // 点击跳转到 Details
      />                                                            // 结束 Button
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 HomeScreen

function DetailsScreen({ navigation }) {                            // 定义 Details 页面,接收 navigation
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>详情页</Text>                      // 页面标题
      <Button                                                       // 按钮
        title="返回首页"                                            // 按钮文字
        onPress={() => navigation.goBack()}                         // 点击返回上一页
      />                                                            // 结束 Button
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 DetailsScreen

const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Stack.Navigator>                                             // 堆栈导航器
        <Stack.Screen name="Home" component={HomeScreen} />        // 注册 Home 页面
        <Stack.Screen name="Details" component={DetailsScreen} />  // 注册 Details 页面
      </Stack.Navigator>                                            // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center' },  // 居中容器样式
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },    // 标题样式
});                                                                 // 结束样式表

每个页面组件会自动收到一个 navigation 对象,包含导航方法。

2.3 导航方法对比

方法行为什么时候用
navigate('Name')已经在 Name 页面则更新参数;否则压入堆栈日常跳转,最常用
push('Name')总是压入一个新页面同一页面需要重复打开(比如看不同商品)
goBack()返回上一页用户主动返回
popToTop()回到堆栈最底部回到首页

2.4 配置页面标题

// JavaScript + React Native (JSX)
<Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: '#4A90D9' }, headerTintColor: '#fff' }}>  // 统一配置头部样式
  <Stack.Screen                                                     // Home 页面
    name="Home"                                                     // 路由名称
    component={HomeScreen}                                          // 页面组件
    options={{ title: '首页' }}                                     // 单独配置标题
  />                                                                // 结束 Home
  <Stack.Screen                                                     // Details 页面
    name="Details"                                                  // 路由名称
    component={DetailsScreen}                                       // 页面组件
    options={{ title: '详情' }}                                     // 单独配置标题
  />                                                                // 结束 Details
</Stack.Navigator>                                                  // 结束导航器

screenOptions 作用于所有页面,options 只作用于当前页面。两者同时存在时,options 优先级更高。


三、页面间参数传递

3.1 传递参数

导航时携带数据:

// JavaScript + React Native
navigation.navigate('Details', {                                    // 跳转到 Details
  itemId: 86,                                                       // 传递 itemId
  title: 'React Native 入门',                                       // 传递 title
});                                                                 // 结束 navigate

第二个参数就是传递给目标页面的数据。

3.2 读取参数

目标页面通过 route.params 读取:

// JavaScript + React Native
function DetailsScreen({ route }) {                                 // 定义详情页面,接收 route
  const { itemId, title } = route.params;                           // 从 route.params 中解构参数

  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Text>ID: {itemId}</Text>                                     // 显示 ID
      <Text>标题: {title}</Text>                                    // 显示标题
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 DetailsScreen

每个页面组件会自动收到 route 对象,route.params 就是导航时传入的参数。

3.3 完整示例:文章列表到详情

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器
import { View, Text, Button, FlatList, Pressable, StyleSheet } from 'react-native';  // 引入需要的组件

function HomeScreen({ navigation }) {                               // 定义 Home 页面
  const posts = [                                                   // 文章列表数据
    { id: '1', title: 'React Native 入门' },                        // 第一篇文章
    { id: '2', title: '组件与 Props' },                             // 第二篇文章
    { id: '3', title: '列表与导航' },                               // 第三篇文章
  ];                                                                // 结束数组

  return (                                                          // 返回界面描述
    <FlatList                                                       // 高效列表
      data={posts}                                                  // 数据源
      keyExtractor={(item) => item.id}                              // 从每项提取唯一 key
      renderItem={({ item }) => (                                   // 如何渲染每一项
        <Pressable                                                   // 可点击容器
          style={styles.item}                                       // 应用 item 样式
          onPress={() =>                                           // 点击回调
            navigation.navigate('Details', {                        // 跳转到 Details
              itemId: item.id,                                      // 传递 id
              title: item.title,                                    // 传递标题
            })                                                      // 结束 navigate
          }                                                         // 结束回调
        >                                                           // 开始 Pressable
          <Text style={styles.itemText}>{item.title}</Text>         // 显示文章标题
        </Pressable>                                                // 结束 Pressable
      )}                                                            // 结束 renderItem
    />                                                              // 结束 FlatList
  );                                                                // 结束 return
}                                                                   // 结束 HomeScreen

function DetailsScreen({ route, navigation }) {                     // 定义 Details 页面
  const { itemId, title } = route.params;                           // 从路由参数中解构

  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>{title}</Text>                     // 显示文章标题
      <Text>文章 ID: {itemId}</Text>                                // 显示文章 ID
      <Button title="返回" onPress={() => navigation.goBack()} />   // 返回按钮
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 DetailsScreen

const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Stack.Navigator>                                             // 堆栈导航器
        <Stack.Screen name="Home" component={HomeScreen} />        // 注册 Home 页面
        <Stack.Screen name="Details" component={DetailsScreen} />  // 注册 Details 页面
      </Stack.Navigator>                                            // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center' },  // 居中容器样式
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },     // 标题样式
  item: { padding: 16, borderBottomWidth: 1, borderBottomColor: '#eee' },  // 列表项样式
  itemText: { fontSize: 18 },                                       // 列表项文字样式
});                                                                 // 结束样式表

这就是"列表 → 详情"的标准流程:列表页通过 navigate 传递参数,详情页通过 route.params 读取。

3.4 参数的注意事项

传递的参数最好是可序列化的(字符串、数字、布尔值、简单对象)。避免传递函数、类实例等不可序列化的值,否则可能影响深度链接和状态持久化。


四、Bottom Tab Navigator:底部标签导航

4.1 什么是标签导航

底部标签导航在屏幕底部显示一排标签,点击切换不同模块。微信、微博、抖音的底部导航都是这种形式。

特点:

  • 每个标签对应一个独立模块;
  • 标签之间平级,没有"返回"的概念;
  • 标签页面懒加载,首次访问时才挂载,之后保留在内存中。

4.2 基本用法

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';  // 引入底部标签导航器
import { View, Text, StyleSheet } from 'react-native';              // 引入需要的组件

function HomeScreen() {                                             // 定义 Home 页面
  return <View style={styles.center}><Text>首页</Text></View>;      // 返回居中文字
}                                                                   // 结束 HomeScreen

function SearchScreen() {                                           // 定义 Search 页面
  return <View style={styles.center}><Text>搜索</Text></View>;      // 返回居中文字
}                                                                   // 结束 SearchScreen

function ProfileScreen() {                                          // 定义 Profile 页面
  return <View style={styles.center}><Text>个人中心</Text></View>;  // 返回居中文字
}                                                                   // 结束 ProfileScreen

const Tab = createBottomTabNavigator();                             // 创建标签导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Tab.Navigator>                                               // 标签导航器
        <Tab.Screen name="Home" component={HomeScreen} />          // 注册 Home 标签
        <Tab.Screen name="Search" component={SearchScreen} />      // 注册 Search 标签
        <Tab.Screen name="Profile" component={ProfileScreen} />    // 注册 Profile 标签
      </Tab.Navigator>                                              // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center' },  // 居中容器样式
});                                                                 // 结束样式表

4.3 自定义标签图标

// JavaScript + React Native (JSX)
<Tab.Navigator                                                      // 标签导航器
  screenOptions={({ route }) => ({                                  // 统一配置
    tabBarIcon: ({ color, size }) => {                              // 图标渲染函数
      const icons = { Home: '🏠', Search: '🔍', Profile: '👤' };   // 图标映射表
      return <Text style={{ fontSize: size, color }}>{icons[route.name]}</Text>;  // 根据路由名返回图标
    },                                                              // 结束 tabBarIcon
  })}                                                               // 结束 screenOptions
>                                                                   // 结束开始标签
  {/* 页面定义 */}
</Tab.Navigator>                                                    // 结束导航器

实际项目中通常使用 @expo/vector-icons 或 react-native-vector-icons 来显示图标。

4.4 标签导航与堆栈导航的配合

标签导航通常不单独使用。更常见的模式是:每个标签内部再嵌套一个堆栈导航。

为什么? 因为标签之间是平级的,但每个模块内部可能有自己的页面流程。比如"首页"标签内部需要从文章列表跳到文章详情,这就需要 Stack 来管理。


五、Drawer Navigator:抽屉导航

5.1 什么是抽屉导航

抽屉导航从屏幕边缘滑出一个侧边菜单,通常包含全局导航选项。在 Android 上通常通过边缘滑动打开,在 iOS 上也可以通过头部按钮打开。

5.2 基本用法

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createDrawerNavigator } from '@react-navigation/drawer';  // 引入抽屉导航器
import { View, Text, StyleSheet } from 'react-native';              // 引入需要的组件

function HomeScreen() {                                             // 定义 Home 页面
  return <View style={styles.center}><Text>首页</Text></View>;      // 返回居中文字
}                                                                   // 结束 HomeScreen

function SettingsScreen() {                                         // 定义 Settings 页面
  return <View style={styles.center}><Text>设置</Text></View>;      // 返回居中文字
}                                                                   // 结束 SettingsScreen

const Drawer = createDrawerNavigator();                             // 创建抽屉导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Drawer.Navigator>                                            // 抽屉导航器
        <Drawer.Screen name="Home" component={HomeScreen} />       // 注册 Home 页面
        <Drawer.Screen name="Settings" component={SettingsScreen} />  // 注册 Settings 页面
      </Drawer.Navigator>                                           // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center' },  // 居中容器样式
});                                                                 // 结束样式表

5.3 三种导航器的对比

导航器适用场景页面关系类比
Stack列表 → 详情堆叠,有返回一摞卡片
Bottom Tab多个平级模块平级切换一排抽屉
Drawer全局菜单侧边滑出一个侧柜

实际项目中,三种导航器通常嵌套使用:最外层是 Drawer 或 Tab,内部嵌套 Stack。


六、嵌套导航器

6.1 为什么要嵌套

真实 App 的导航结构很少只有一种导航器。常见的结构是:

  • 最外层:Tab Navigator(首页、搜索、个人中心);
  • 首页内部:Stack Navigator(首页列表 → 详情页);
  • 个人中心内部:Stack Navigator(个人资料 → 设置)。

这就是嵌套导航器:一个导航器出现在另一个导航器的页面中。

6.2 嵌套示例

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';  // 引入底部标签导航器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器
import { View, Text, Button, StyleSheet } from 'react-native';      // 引入需要的组件

const Tab = createBottomTabNavigator();                             // 创建标签导航器对象
const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

function FeedScreen({ navigation }) {                               // 定义 Feed 列表页面
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text>动态列表</Text>                                          // 页面文字
      <Button                                                       // 按钮
        title="查看动态详情"                                        // 按钮文字
        onPress={() => navigation.navigate('FeedDetails', { id: '1' })}  // 跳转详情页
      />                                                            // 结束 Button
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 FeedScreen

function FeedDetailsScreen({ route }) {                             // 定义 Feed 详情页面
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text>动态详情 ID: {route.params.id}</Text>                    // 显示详情 ID
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 FeedDetailsScreen

function FeedStack() {                                              // 定义 Feed 的堆栈导航器
  return (                                                          // 返回界面描述
    <Stack.Navigator>                                               // 堆栈导航器
      <Stack.Screen name="FeedList" component={FeedScreen} options={{ title: '动态' }} />  // 列表页面
      <Stack.Screen name="FeedDetails" component={FeedDetailsScreen} options={{ title: '详情' }} />  // 详情页面
    </Stack.Navigator>                                              // 结束导航器
  );                                                                // 结束 return
}                                                                   // 结束 FeedStack

function ProfileScreen() {                                          // 定义 Profile 页面
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text>个人中心</Text>                                          // 页面文字
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 ProfileScreen

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Tab.Navigator>                                               // 标签导航器
        <Tab.Screen name="FeedTab" component={FeedStack} options={{ title: '动态' }} />  // Feed 标签,内嵌堆栈
        <Tab.Screen name="ProfileTab" component={ProfileScreen} options={{ title: '我的' }} />  // Profile 标签
      </Tab.Navigator>                                              // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center' },  // 居中容器样式
});                                                                 // 结束样式表

结构如下:

// 概念表达
Tab.Navigator                                                       // 外层标签导航器
├── FeedTab -> Stack.Navigator                                      // Feed 标签内嵌堆栈导航器
│   ├── FeedList                                                    // 列表页面
│   └── FeedDetails                                                 // 详情页面
└── ProfileTab -> ProfileScreen                                     // Profile 标签

在 Feed 标签内可以跳转到详情页,底部标签栏仍然显示。切换到 Profile 标签再切回来,Feed 的堆栈状态会保留。

6.3 跨导航器跳转

如果需要从一个导航器跳转到另一个导航器中的页面,可以嵌套 navigate:

// JavaScript + React Native
navigation.navigate('ProfileTab', {                                 // 目标标签
  screen: 'ProfileDetails',                                         // 目标页面
  params: { userId: '123' },                                        // 传递参数
});                                                                 // 结束 navigate

这会先切换到 ProfileTab,再在它的内部堆栈中打开 ProfileDetails 页面。

6.4 常见嵌套结构

// 概念表达
Tab.Navigator (底部标签)
├── 首页 -> Stack.Navigator (首页堆栈)
│   ├── HomeMain (首页)
│   └── HomeDetail (首页详情)
├── 发现 -> Stack.Navigator (发现堆栈)
│   ├── DiscoverMain (发现)
│   └── DiscoverDetail (发现详情)
└── 我的 -> Stack.Navigator (我的堆栈)
    ├── ProfileMain (个人主页)
    └── Settings (设置)

每个标签内部有自己的页面流程,标签之间平级切换。这是 React Native App 的标准结构。


七、认证流程:状态驱动导航

7.1 什么是认证流程

很多 App 需要在登录后才能访问主要功能。React Navigation 推荐的做法是:根据登录状态,条件性地渲染不同的页面组。

注意关键词:条件渲染。这和"视图 = 函数(状态)"完全一致。

7.2 实现思路

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器
import { useState } from 'react';                                   // 引入 useState
import { View, Text, Button, TextInput, StyleSheet } from 'react-native';  // 引入需要的组件

function SignInScreen({ onSignIn }) {                               // 定义登录页面
  const [username, setUsername] = useState('');                     // 创建状态 username,初始空字符串

  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>登录</Text>                         // 标题
      <TextInput                                                    // 输入框
        style={styles.input}                                        // 应用 input 样式
        placeholder="用户名"                                         // 占位提示
        value={username}                                            // 值由 username 决定
        onChangeText={setUsername}                                  // 输入变化时更新 username
      />                                                            // 结束 TextInput
      <Button title="登录" onPress={() => onSignIn(username)} />    // 点击登录
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 SignInScreen

function HomeScreen({ onSignOut }) {                                // 定义主页面
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>欢迎回来</Text>                     // 欢迎文字
      <Button title="退出登录" onPress={onSignOut} />                // 退出按钮
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 HomeScreen

const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

export default function App() {                                     // 定义并默认导出 App
  const [user, setUser] = useState(null);                           // 创建状态 user,初始 null

  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Stack.Navigator>                                             // 堆栈导航器
        {user ? (                                                   // 如果已登录
          <Stack.Screen name="Home">                                // 注册 Home 页面
            {(props) => <HomeScreen {...props} onSignOut={() => setUser(null)} />}  // 传入退出回调
          </Stack.Screen>                                           // 结束 Home
        ) : (                                                       // 否则
          <Stack.Screen name="SignIn">                              // 注册 SignIn 页面
            {(props) => <SignInScreen {...props} onSignIn={(name) => setUser(name)} />}  // 传入登录回调
          </Stack.Screen>                                           // 结束 SignIn
        )}                                                          // 结束条件
      </Stack.Navigator>                                            // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },  // 居中容器样式
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },    // 标题样式
  input: {                                                          // 输入框样式
    width: '100%',                                                  // 宽度占满
    borderWidth: 1,                                                 // 边框宽度 1
    borderColor: '#ccc',                                            // 边框颜色
    borderRadius: 6,                                                // 圆角 6
    paddingHorizontal: 10,                                          // 水平内边距 10
    height: 40,                                                     // 高度 40
    marginBottom: 12,                                               // 底部间距 12
  },                                                                // 结束 input 样式
});                                                                 // 结束样式表

核心逻辑:

  • user 为 null 时,只渲染登录页面;
  • user 有值时,只渲染主应用页面;
  • 登录成功后 setUser,导航器自动切换到主应用;
  • 退出登录时 setUser(null),导航器自动切回登录页面。

不需要手动调用 navigate。 状态变了,导航器条件渲染的结果就变了。这又是"视图 = 函数(状态)"在导航层面的体现。

7.3 持久化登录状态

结合第10课会讲的 SecureStore,可以在应用启动时读取保存的令牌,恢复登录状态:

// JavaScript + React Native
useEffect(() => {                                                   // 注册副作用
  SecureStore.getItemAsync('authToken')                             // 读取令牌
    .then((token) => setUser(token ? { token } : null))             // 有令牌就设为已登录
    .finally(() => setLoading(false));                              // 关闭加载状态
}, []);                                                             // 空数组

loading 状态在令牌读取完成前阻止渲染,避免闪烁。


八、导航 Hook

除了通过 props 获取 navigation,React Navigation 还提供了 Hook 版本:

// JavaScript + React Native
import { useNavigation, useRoute } from '@react-navigation/native';  // 引入两个 Hook

function MyScreen() {                                               // 定义组件
  const navigation = useNavigation();                               // 获取 navigation 对象
  const route = useRoute();                                         // 获取 route 对象

  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Text>当前页面:{route.name}</Text>                            // 显示当前页面名
      <Button                                                       // 按钮
        title="跳转"                                                // 按钮文字
        onPress={() => navigation.navigate('Details')}              // 点击跳转
      />                                                            // 结束 Button
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 MyScreen

useNavigation() 在组件需要访问导航对象、但又不是页面组件本身时特别有用,比如嵌套在页面内部的子组件。


九、导航与"视图 = 函数(状态)"

把导航放回"视图 = 函数(状态)"的框架里:

  • 导航状态(当前在哪个页面、堆栈里有哪些页面)也是一种状态;
  • NavigationContainer 管理这个状态;
  • 跳转、返回、切换标签,本质上都是更新导航状态;
  • 导航状态变化,导航器重新计算,显示对应的页面。
// 概念表达
导航视图 = 导航器(导航状态)                                          // 页面由导航状态决定

条件渲染认证流程就是最好的例子:user 状态决定显示登录页面还是主应用页面。状态是输入,页面是输出。


十、练习题

练习题1:选择题——三种导航器的区别

题目:下面哪一项最适合描述"列表 → 详情"的页面流程?

A. Bottom Tab Navigator
B. Drawer Navigator
C. Stack Navigator
D. 以上都不适合

参考答案:C

解读:Stack Navigator 模拟"一摞卡片"的行为,适合顺序流程,比如列表页面跳转到详情页面,然后返回。


练习题2:填空题——导航方法

题目:补全代码,从 Home 页面跳转到 Details 页面。

// JavaScript + React Native (JSX)
<Button                                                             // 按钮
  title="查看详情"                                                  // 按钮文字
  onPress={() => navigation.____('Details')}                        // 补全导航方法
/>                                                                  // 结束 Button

参考答案:navigate

解读:navigation.navigate('Details') 是跳转到指定页面的标准方法。


练习题3:改错题——参数传递

题目:下面的代码想传递参数,但读取时出错了。请找出问题。

// JavaScript + React Native
navigation.navigate('Details', { itemId: 86 });                     // 跳转时传递 itemId

function DetailsScreen({ route }) {                                 // 定义 DetailsScreen
  const itemId = route.itemId;                                      // 错误:应该从 route.params 读取
}                                                                   // 结束

参考答案:应该写成 const { itemId } = route.params;。

解读:导航参数通过 route.params 访问,不是直接放在 route 上。


练习题4:代码阅读题——导航结构

题目:阅读下面的导航结构,说出在 FeedTab 中点击"查看动态详情"后,底部标签栏是否还显示。

// JavaScript + React Native (JSX)
<Tab.Navigator>                                                     // 标签导航器
  <Tab.Screen name="FeedTab" component={FeedStack} />              // Feed 标签,内嵌堆栈
  <Tab.Screen name="ProfileTab" component={ProfileScreen} />       // Profile 标签
</Tab.Navigator>                                                    // 结束导航器

其中 FeedStack 是:

// JavaScript + React Native (JSX)
<Stack.Navigator>                                                   // 堆栈导航器
  <Stack.Screen name="FeedList" component={FeedScreen} />          // 列表页面
  <Stack.Screen name="FeedDetails" component={FeedDetailsScreen} />  // 详情页面
</Stack.Navigator>                                                  // 结束导航器

参考答案:底部标签栏仍然显示。

解读:FeedStack 嵌套在 Tab 的 FeedTab 内部。在 FeedStack 内跳转时,外层 Tab 不受影响,标签栏保持可见。


练习题5:动手题——创建 Stack Navigator

题目:创建一个包含 Home 和 About 两个页面的 Stack Navigator,Home 页面有一个按钮跳转到 About。

参考答案:

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器
import { View, Text, Button } from 'react-native';                  // 引入需要的组件

function HomeScreen({ navigation }) {                               // 定义 Home 页面
  return (                                                          // 返回界面描述
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>  // 居中容器
      <Text>首页</Text>                                              // 页面文字
      <Button title="去关于页" onPress={() => navigation.navigate('About')} />  // 跳转按钮
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 HomeScreen

function AboutScreen() {                                            // 定义 About 页面
  return (                                                          // 返回界面描述
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>  // 居中容器
      <Text>关于页</Text>                                            // 页面文字
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 AboutScreen

const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Stack.Navigator>                                             // 堆栈导航器
        <Stack.Screen name="Home" component={HomeScreen} />        // 注册 Home 页面
        <Stack.Screen name="About" component={AboutScreen} />      // 注册 About 页面
      </Stack.Navigator>                                            // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

解读:NavigationContainer 是必需的外层容器。Stack.Navigator 定义页面集合,Stack.Screen 注册每个页面。


练习题6:简答题——导航方法与状态的关系

题目:为什么在认证流程中,登录成功后不需要手动调用 navigate?

参考答案:因为认证流程使用条件渲染。user 状态为 null 时渲染登录页面,有值时渲染主应用页面。登录成功更新 user 状态后,导航器自动重新计算,切换到主应用页面。

解读:这是"视图 = 函数(状态)"在导航层面的体现。导航状态变化,导航器显示对应的页面,不需要手动跳转。


练习题7:参数传递题——列表到详情

题目:有一个文章列表,点击某篇文章时跳转到详情页并传递文章的 id 和 title。

参考答案:

// JavaScript + React Native (JSX)
<Pressable                                                          // 可点击容器
  onPress={() =>                                                   // 点击回调
    navigation.navigate('Details', { id: item.id, title: item.title })  // 跳转并传递参数
  }                                                                 // 结束回调
>                                                                   // 开始 Pressable
  <Text>{item.title}</Text>                                         // 显示文章标题
</Pressable>                                                        // 结束 Pressable

解读:导航方法的第二个参数是传递的数据。目标页面通过 route.params 读取。


练习题8:读取参数题

题目:在详情页面中读取导航传递的 id 和 title 参数。

参考答案:

// JavaScript + React Native
function DetailsScreen({ route }) {                                 // 定义详情页面,接收 route
  const { id, title } = route.params;                               // 从 route.params 中解构参数

  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Text>ID: {id}</Text>                                         // 显示 ID
      <Text>标题: {title}</Text>                                    // 显示标题
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 DetailsScreen

解读:route.params 包含导航时传入的所有参数。使用解构可以方便地取出需要的值。


练习题9:配置标题题

题目:为 Stack Navigator 中的 Home 页面配置标题为"首页"。

参考答案:

// JavaScript + React Native (JSX)
<Stack.Screen                                                       // Home 页面
  name="Home"                                                       // 路由名称
  component={HomeScreen}                                            // 页面组件
  options={{ title: '首页' }}                                       // 设置标题
/>                                                                  // 结束 Home

解读:options.title 设置页面在导航栏中显示的标题。也可以在整个 Stack.Navigator 上设置 screenOptions 来统一配置。


练习题10:Tab Navigator 题

题目:创建一个包含三个标签的底部标签导航:首页、搜索、我的。

参考答案:

// JavaScript + React Native
const Tab = createBottomTabNavigator();                             // 创建标签导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Tab.Navigator>                                               // 标签导航器
        <Tab.Screen name="Home" component={HomeScreen} />          // 注册 Home 标签
        <Tab.Screen name="Search" component={SearchScreen} />      // 注册 Search 标签
        <Tab.Screen name="Profile" component={ProfileScreen} />    // 注册 Profile 标签
      </Tab.Navigator>                                              // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

解读:Tab Navigator 适合平级模块的切换。每个标签对应一个独立的页面组件。


练习题11:嵌套导航题

题目:在 Tab Navigator 的 Home 标签中嵌套一个 Stack Navigator,包含 HomeList 和 HomeDetails 两个页面。

参考答案:

// JavaScript + React Native
const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

function HomeStack() {                                              // 定义 Home 的堆栈导航器
  return (                                                          // 返回界面描述
    <Stack.Navigator>                                               // 堆栈导航器
      <Stack.Screen name="HomeList" component={HomeListScreen} />  // 列表页面
      <Stack.Screen name="HomeDetails" component={HomeDetailsScreen} />  // 详情页面
    </Stack.Navigator>                                              // 结束导航器
  );                                                                // 结束 return
}                                                                   // 结束 HomeStack

const Tab = createBottomTabNavigator();                             // 创建标签导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Tab.Navigator>                                               // 标签导航器
        <Tab.Screen name="Home" component={HomeStack} />           // Home 标签,内嵌堆栈
        <Tab.Screen name="Profile" component={ProfileScreen} />    // Profile 标签
      </Tab.Navigator>                                              // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

解读:嵌套导航器是真实 App 的常见结构。外层 Tab 负责模块切换,内层 Stack 负责模块内的页面流程。


练习题12:导航 Hook 题

题目:在页面内部的子组件中获取 navigation 对象。

参考答案:

// JavaScript + React Native
import { useNavigation } from '@react-navigation/native';          // 引入 useNavigation Hook

function MyButton() {                                               // 定义子组件
  const navigation = useNavigation();                               // 获取 navigation 对象

  return (                                                          // 返回界面描述
    <Button                                                         // 按钮
      title="跳转"                                                  // 按钮文字
      onPress={() => navigation.navigate('Details')}                // 点击跳转
    />                                                              // 结束 Button
  );                                                                // 结束 return
}                                                                   // 结束 MyButton

解读:useNavigation 让非页面组件也能访问导航对象。在页面内部的深层子组件中特别有用。


练习题13:认证流程题

题目:用 user 状态控制显示登录页面还是主应用页面。

参考答案:

// JavaScript + React Native (JSX)
<Stack.Navigator>                                                   // 堆栈导航器
  {user ? (                                                         // 如果已登录
    <Stack.Screen name="Home" component={HomeScreen} />            // 渲染主应用
  ) : (                                                             // 否则
    <Stack.Screen name="SignIn" component={SignInScreen} />        // 渲染登录页面
  )}                                                                // 结束条件
</Stack.Navigator>                                                  // 结束导航器

解读:条件渲染认证流程不需要手动 navigate。user 状态变化后,导航器自动切换。


练习题14:综合导航结构题

题目:设计一个包含以下结构的导航:

  • 底部标签:首页、我的;
  • 首页内部:文章列表 → 文章详情;
  • 我的内部:个人资料 → 设置。

参考答案:

// JavaScript + React Native
const Tab = createBottomTabNavigator();                             // 创建标签导航器对象
const Stack = createNativeStackNavigator();                         // 创建堆栈导航器对象

function HomeStack() {                                              // 定义首页的堆栈导航器
  return (                                                          // 返回界面描述
    <Stack.Navigator>                                               // 堆栈导航器
      <Stack.Screen name="ArticleList" component={ArticleListScreen} />  // 列表页面
      <Stack.Screen name="ArticleDetail" component={ArticleDetailScreen} />  // 详情页面
    </Stack.Navigator>                                              // 结束导航器
  );                                                                // 结束 return
}                                                                   // 结束 HomeStack

function ProfileStack() {                                           // 定义我的页面的堆栈导航器
  return (                                                          // 返回界面描述
    <Stack.Navigator>                                               // 堆栈导航器
      <Stack.Screen name="ProfileMain" component={ProfileScreen} />  // 个人资料页面
      <Stack.Screen name="Settings" component={SettingsScreen} />  // 设置页面
    </Stack.Navigator>                                              // 结束导航器
  );                                                                // 结束 return
}                                                                   // 结束 ProfileStack

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Tab.Navigator>                                               // 标签导航器
        <Tab.Screen name="Home" component={HomeStack} />           // 首页标签
        <Tab.Screen name="Profile" component={ProfileStack} />     // 我的标签
      </Tab.Navigator>                                              // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

解读:这是典型的"Tab 嵌套 Stack"结构。每个标签内部有独立的页面流程,标签之间平级切换。


练习题15:综合应用题——带导航的迷你博客

题目:创建一个迷你博客应用:

  1. 底部有两个标签:文章和关于;
  2. 文章标签内是一个文章列表,点击文章跳转到详情页;
  3. 详情页显示文章标题和正文;
  4. 关于标签显示应用介绍。

参考答案:

// JavaScript + React Native
import { NavigationContainer } from '@react-navigation/native';    // 引入导航容器
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';  // 引入底部标签导航器
import { createNativeStackNavigator } from '@react-navigation/native-stack';  // 引入堆栈导航器
import { View, Text, FlatList, Pressable, StyleSheet } from 'react-native';  // 引入需要的组件

const posts = [                                                     // 文章数据
  { id: '1', title: 'React Native 入门', body: 'React Native 让你用 JavaScript 构建原生应用。' },  // 第一篇文章
  { id: '2', title: '组件与 Props', body: '组件是 React Native 的基本单元,props 用于传递数据。' },  // 第二篇文章
  { id: '3', title: '导航基础', body: 'React Navigation 是主流的导航方案。' },  // 第三篇文章
];                                                                  // 结束数据

function ArticleListScreen({ navigation }) {                        // 定义文章列表页面
  return (                                                          // 返回界面描述
    <FlatList                                                       // 高效列表
      data={posts}                                                  // 数据源
      keyExtractor={(item) => item.id}                              // 从每项提取唯一 key
      renderItem={({ item }) => (                                   // 如何渲染每一项
        <Pressable                                                   // 可点击容器
          style={styles.item}                                       // 应用 item 样式
          onPress={() => navigation.navigate('ArticleDetail', { id: item.id })}  // 跳转详情页
        >                                                           // 开始 Pressable
          <Text style={styles.itemTitle}>{item.title}</Text>         // 显示标题
        </Pressable>                                                // 结束 Pressable
      )}                                                            // 结束 renderItem
      ItemSeparatorComponent={() => <View style={styles.separator} />}  // 分隔线
    />                                                              // 结束 FlatList
  );                                                                // 结束 return
}                                                                   // 结束 ArticleListScreen

function ArticleDetailScreen({ route }) {                           // 定义文章详情页面
  const { id } = route.params;                                      // 从路由参数中取出 id
  const post = posts.find((p) => p.id === id);                      // 根据 id 找到文章

  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>{post.title}</Text>                // 显示标题
      <Text style={styles.body}>{post.body}</Text>                  // 显示正文
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 ArticleDetailScreen

function AboutScreen() {                                            // 定义关于页面
  return (                                                          // 返回界面描述
    <View style={styles.center}>                                    // 居中容器
      <Text style={styles.title}>关于</Text>                        // 页面标题
      <Text>一个用 React Navigation 构建的迷你博客。</Text>          // 应用介绍
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 AboutScreen

const HomeStack = createNativeStackNavigator();                     // 创建首页堆栈导航器对象

function HomeStackScreen() {                                        // 定义首页堆栈
  return (                                                          // 返回界面描述
    <HomeStack.Navigator>                                           // 堆栈导航器
      <HomeStack.Screen                                             // 列表页面
        name="ArticleList"                                          // 路由名称
        component={ArticleListScreen}                               // 页面组件
        options={{ title: '文章' }}                                 // 页面标题
      />                                                            // 结束列表页面
      <HomeStack.Screen                                             // 详情页面
        name="ArticleDetail"                                        // 路由名称
        component={ArticleDetailScreen}                             // 页面组件
        options={{ title: '详情' }}                                 // 页面标题
      />                                                            // 结束详情页面
    </HomeStack.Navigator>                                          // 结束导航器
  );                                                                // 结束 return
}                                                                   // 结束 HomeStackScreen

const Tab = createBottomTabNavigator();                             // 创建标签导航器对象

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <NavigationContainer>                                           // 导航容器
      <Tab.Navigator>                                               // 标签导航器
        <Tab.Screen                                                 // 文章标签
          name="Home"                                               // 路由名称
          component={HomeStackScreen}                               // 组件是首页堆栈
          options={{ title: '文章' }}                               // 标签标题
        />                                                          // 结束文章标签
        <Tab.Screen                                                 // 关于标签
          name="About"                                              // 路由名称
          component={AboutScreen}                                   // 关于页面
          options={{ title: '关于' }}                               // 标签标题
        />                                                          // 结束关于标签
      </Tab.Navigator>                                              // 结束导航器
    </NavigationContainer>                                          // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App

const styles = StyleSheet.create({                                  // 创建样式表
  center: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },  // 居中容器样式
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 12 },    // 标题样式
  body: { fontSize: 16, lineHeight: 24, color: '#444' },            // 正文样式
  item: { padding: 16 },                                            // 列表项样式
  itemTitle: { fontSize: 18 },                                      // 列表项标题样式
  separator: { height: 1, backgroundColor: '#eee' },                // 分隔线样式
});                                                                 // 结束样式表

解读:这个应用综合运用了 Stack Navigator、Bottom Tab Navigator、嵌套导航、参数传递和列表渲染。文章标签内部用 Stack 实现列表到详情的流程,关于标签是一个独立页面。底部标签栏在两个模块之间切换。


十一、总结

这一课我们学习了 React Navigation 的核心用法:

  1. React Navigation 是 React Native 的主流导航方案;
  2. Stack Navigator 适合顺序流程,模拟"一摞卡片";
  3. 页面间通过 navigation.navigate('Name', params) 传递参数,通过 route.params 读取;
  4. Bottom Tab Navigator 适合平级模块切换;
  5. Drawer Navigator 适合全局菜单;
  6. 嵌套导航器是真实 App 的常见结构,Tab 嵌套 Stack 尤其常见;
  7. 认证流程利用条件渲染,不需要手动跳转;
  8. useNavigation 让非页面组件也能访问导航对象。

记住一句话:

导航也是一种状态,页面切换就是导航状态的变化。


第6课预告

下一课,我们进入 【React Native娓娓道来】第6课 状态管理的进阶之路:Context、useReducer 与跨组件共享状态。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐