React Native 导航

要点

栈导航表达进入与返回,标签表达并列入口。路由参数适合传资源 ID,不适合承载整份可变业务对象。

⚠️ 适用范围:本篇 React Navigation 直接用法适用于 bare 工程 / React Navigation 模板工程;Expo 主线工程使用内置 expo-router(等价写法对照见下文),自 Expo SDK 56 起官方不再支持从应用代码直接 import @react-navigation/* 包——与本模块 README 技术基线 一致。

安装

⚠️ 适用范围(与 README 技术基线 一致):本篇直接安装并 import @react-navigation/* 包的写法适用于 bare 工程(RN CLI)与 React Navigation 模板工程。Expo 主线工程(create-expo-app 默认模板)的导航由内置的 expo-router 承担——自 Expo SDK 56 起不再支持从应用代码直接 import @react-navigation/* 包,两者混装是 expo-doctor 会标记的非预期配对。Expo 工程的学习者请通过本篇理解导航器模型,动手时使用下文的 expo-router 等价写法,不要在本节的安装步骤上操作。

# 核心包(仅 bare 工程 / React Navigation 模板工程需要)
npm install @react-navigation/native

1. 三端原生依赖(bare 工程用 npm install;prebuild 的 Expo 工程可用 npx expo install 对齐版本)
npm install react-native-screens react-native-safe-area-context

1. 按导航器类型追加安装
npm install @react-navigation/native-stack   # 原生栈
npm install @react-navigation/bottom-tabs    # 底部标签
npm install @react-navigation/drawer         # 抽屉(额外依赖 react-native-gesture-handler、react-native-reanimated)

安装完成后在入口文件用 NavigationContainer 包裹整棵应用树:

// App.tsx
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return <NavigationContainer>{/* 导航器在此嵌套 */}</NavigationContainer>;
}

Expo 主线工程(expo-router)等价写法:expo-router 不需要 NavigationContainer,也无需手工安装导航包,把上述概念映射到文件路由即可:

本篇 React Navigation 概念expo-router 等价实现
NavigationContainer 包裹app/_layout.tsx 根布局(内置容器)
createNativeStackNavigatorapp/_layout.tsx 中的 <Stack>
createBottomTabNavigatorapp/(tabs)/_layout.tsx 中的 <Tabs>
navigation.navigate('Detail', { id })router.push('/detail/42') 或 <Link href>
route.paramsuseLocalSearchParams()

完整对照与用法见 Expo 要点 — expo-router 与 生态集成。

核心概念:导航器的嵌套模型

定义: React Navigation 中每个导航器(Navigator)管理一组页面(Screen),导航器之间可以嵌套——这是搭建复杂结构的基本手段。

典型结构:外层原生栈做”启动页 → 主界面 → 详情页”的跳转,主界面内嵌底部标签做一级分区。

NativeStack
├── Splash(启动页)
├── Main(内嵌 BottomTabs)
│   ├── Home
│   ├── Discover
│   └── Profile
└── Detail(详情页,可接收参数)

组合实战:Stack + Tabs

// navigation/RootNavigator.tsx
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

import Home from '../screens/Home';
import Discover from '../screens/Discover';
import Profile from '../screens/Profile';
import Detail from '../screens/Detail';

type RootStackParamList = {
  Main: undefined;
  Detail: { itemId: string; from?: string };
};

const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator();

// 一级分区:底部标签
function MainTabs() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="Home" component={Home} />
      <Tab.Screen name="Discover" component={Discover} />
      <Tab.Screen name="Profile" component={Profile} />
    </Tab.Navigator>
  );
}

// 外层:原生栈
export default function RootNavigator() {
  return (
    <Stack.Navigator initialRouteName="Main">
      <Stack.Screen name="Main" component={MainTabs} options={{ headerShown: false }} />
      <Stack.Screen
        name="Detail"
        component={Detail}
        options={{ title: '详情' }}
      />
    </Stack.Navigator>
  );
}

抽屉导航同理,把 MainTabs 换成 Drawer.Navigator 的内容即可;抽屉需在入口引入 gesture-handler:

// App.tsx 顶部(drawer 方案必须)
import 'react-native-gesture-handler';

页面传参与回调

// screens/Detail.tsx
import { View, Text, Pressable } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';

type Props = NativeStackScreenProps<RootStackParamList, 'Detail'>;

export default function Detail({ route, navigation }: Props) {
  const { itemId, from } = route.params;

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Text>收到参数: {itemId}{from ? `(来自${from})` : ''}</Text>
      {/* 2. 跳转传参 */}
      <Pressable onPress={() => navigation.push('Detail', { itemId: 'next' })}>
        <Text>再压一层 Detail</Text>
      </Pressable>
      {/* 3. 返回上一页 */}
      <Pressable onPress={() => navigation.goBack()}>
        <Text>返回</Text>
      </Pressable>
    </View>
  );
}
// 发起跳转的一侧
navigation.navigate('Detail', { itemId: '42', from: 'Home' });

navigate vs push: push 每次都在当前栈压入一个新 screen;navigate 的目标解析会受当前导航树、嵌套 navigator 与已有路由状态影响,通常适合“前往某功能”,但不要用“它一定回到同名页”推断复杂嵌套的行为。详情页套详情页可用 push;需要精确恢复或替换时,写测试或查看当前 navigation state。

三端导航差异

差异点AndroidiOS鸿蒙
返回手段物理返回键/手势边缘滑动手势侧滑/手势导航
页面切换动画由导航器配置与系统行为共同决定由导航器配置与系统行为共同决定以选定 RNOH 发行版与 ArkUI 壳工程实际行为为准
头部样式Material 风格大标题/毛玻璃鸿蒙设计规范
深链intent-filter 配置Universal Linkswant 隐式跳转配置

处理 Android 物理返回键:

import { BackHandler } from 'react-native';
import { useEffect } from 'react';

useEffect(() => {
  const sub = BackHandler.addEventListener('hardwareBackPress', () => {
    // 返回 true 表示已自行处理(拦截),false 交给默认返回逻辑
    return false;
  });
  return () => sub.remove();
}, []);

栈导航器基于 react-native-screens 使用原生页面容器,返回手势/返回键默认已被正确处理;仅自定义”二次返回退出”等逻辑时才需要手动监听。

最佳实践

导航结构应反映任务关系:列表到详情属于返回栈,平行功能可用 tabs,临时任务可用 modal。没有适用于所有应用的三层上限;检查返回键、深链接和状态恢复能否按预期工作,比分层数量更重要。

路由参数尽量传稳定 id 与必要可序列化值,详情从数据层读取,避免携带过期的大对象。导航和动画依赖按安装版本配置;出现双导航栏时检查每层导航器的显示责任,而不是到处关闭 header。

常见问题

Q1: 为什么跳转后页面是空白?

A: 检查 Screen 的 component 是否真的渲染了内容;再检查是否漏装 react-native-screens/safe-area-context;iOS 新工程还需重跑 pod install。

Q2: 从推送点进来直接打开某页面(冷启动深链)怎么做?

A: 给 NavigationContainer 配 linking 映射 URL 与路由,完整 API 见 navigation-essentials。

Q3: 标签栏想中间凸起一个”发布”按钮?

A: 用 Tab.Screen 自定义 tabBarButton 渲染自定义按钮,样式细节参考 02-components-props。

练习与实践

练习一:三层导航骨架

任务要求: 1. 搭建 Stack + BottomTabs 结构,Tabs 含首页/发现/我的三个页面 2. 首页列表点击跳转 Detail 并传递 id,Detail 内再 push 一层 Detail

评估标准: 返回手势/返回键在每一层都表现正确。

练习二:Tab 间传值

任务要求: 1. “我的”页面修改昵称后,切回”首页”能看到最新昵称 2. 通过全局 store 或 Context 实现(路由参数无法跨 Tab)

提示: Tab 页面是否保持挂载、是否冻结由导航器与 screen 配置决定。不要依赖“切换 tab 必然不重跑 effect”;昵称应来自共享状态或数据层,这样无论页面保活、重挂载还是深链进入,首页都能读取最新值。


相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇