CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
React Native 开发入门/课时/导航与手势
RawBlame
课时目录12 节

React Native 开发入门

  1. 01RN 与 Expo
  2. 02核心组件
  3. 03布局与列表
  4. 04平台差异
  5. 05导航与手势
  6. 06State 与 JSX
  7. 07网络
  8. 08本地存储
  9. 09原生模块
  10. 10动画
  11. 11性能优化
  12. 12测试与发布

导航与手势

Screen nav 与 deep link。

30 分钟

导航与手势

这一课你将能做到

  1. 用 React Navigation 搭建「外层 Tab + 内层 Stack」的标准 App 骨架
  2. 在页面间跳转、传递参数、回传数据,并自定义导航栏
  3. 配置深链接让外部 URL 直达 App 内指定页面,理解 gesture-handler 在手势体系中的作用

为什么要学

App 的本质就是「页面 + 页面之间的流转」。没有导航,你的 App 永远只有一屏。React Navigation 是 RN 事实上的标准导航库——打开任意一个 RN 项目的 package.json,十有八九能看到它。这一课学的不只是 API,更是「一个正经 App 的骨架应该怎么搭」。

人话:Stack 导航像一叠卡片,新页面盖在旧页面上,返回就是抽走最上面那张;Tab 导航像电视遥控器下方的频道按钮,点哪个切哪个,各频道互不干扰、各自保留进度。真实 App 几乎都是「Tab 定骨架、Stack 管层级」的嵌套结构。

先建立图像

NavigationContainer(唯一根容器)
└── Tab.Navigator(底部标签:首页 / 我的)
    ├── HomeStack(首页频道内部的页面栈)
    │   ├── HomeMain(首页列表)
    │   └── Detail(详情页)
    └── ProfileStack(我的频道内部的页面栈)
        ├── ProfileMain(个人主页)
        └── Settings(设置页)

每个频道是一个独立的 Stack,切 Tab 不会丢失各自的浏览进度——你在「首页」点到第 3 层详情,切到「我的」再切回来,还在第 3 层。这就是嵌套导航的意义。

跟着做

步骤 1:安装依赖

npx expo install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs react-native-screens react-native-safe-area-context react-native-gesture-handler

一次装 6 个包别慌:@react-navigation/native 是核心,native-stack 和 bottom-tabs 是两种导航器,react-native-screens 和 react-native-gesture-handler 是把页面切换和手势放到原生线程执行的底座,safe-area-context 负责导航栏高度适配。它们是一个套装,缺一不可。

步骤 2:最小 Stack 导航

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Button, Text, View } from 'react-native';

const Stack = createNativeStackNavigator();

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>首页</Text>
      <Button title="去详情页" onPress={() => navigation.navigate('Detail', { id: 42 })} />
    </View>
  );
}

function DetailScreen({ route }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>详情页,收到 id = {route.params.id}</Text>
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} options={{ title: '首页' }} />
        <Stack.Screen name="Detail" component={DetailScreen} options={{ title: '详情' }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

被 Stack.Screen 注册的组件会自动收到两个 props:navigation(管跳转)和 route(收参数,route.params 里就是传过来的对象)。不用写任何样式,顶部导航栏、标题、iOS 返回箭头和右滑返回手势全都自带。

步骤 3:跳转的四种方式

navigation.navigate('Detail', { id: 1 }); // 常规跳转:目标页已在栈中则回到它
navigation.push('Detail', { id: 2 });     // 强制再压一层新实例
navigation.goBack();                      // 返回上一页
navigation.popToTop();                    // 一路退回到栈底(首页)

navigate 和 push 的区别是最容易踩的坑:从商品 A 的详情页点相关推荐跳商品 B 的详情页,用 navigate 会复用 A 的实例(数据不更新),必须用 push 压一个新层。自己跳自己的场景一律 push。

步骤 4:Tab + Stack 嵌套(真实 App 骨架)

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();
const HomeStack = createNativeStackNavigator();

function HomeStackScreen() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen name="HomeMain" component={HomeScreen} options={{ title: '首页' }} />
      <HomeStack.Screen name="Detail" component={DetailScreen} options={{ title: '详情' }} />
    </HomeStack.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="HomeTab" component={HomeStackScreen} options={{ title: '首页' }} />
        <Tab.Screen name="ProfileTab" component={ProfileScreen} options={{ title: '我的' }} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

结构口诀:Tab 在最外层定骨架,每个 Tab 内部各包一个 Stack 管自己的页面层级。详情页想全屏(不显示底部 Tab 栏),就把它放到外层 Stack 而不是 Tab 内部。

步骤 5:回传数据给上一页

详情页编辑完,通知列表页刷新:

// 详情页:返回前把结果合并到上一页的 params
navigation.navigate({ name: 'HomeMain', params: { updated: true }, merge: true });
navigation.goBack();

// 列表页:监听 params 变化触发刷新
useEffect(() => {
  if (route.params?.updated) {
    fetchList();
    navigation.setParams({ updated: false }); // 消费掉,避免重复刷新
  }
}, [route.params?.updated]);

merge: true 表示把新参数合并进目标页已有的 params,而不是整个替换。注意这适合「通知一下」的轻量场景;如果是复杂数据共享,该上 Context 或状态库(第 6 课)。

步骤 6:子组件里拿导航 + 自定义导航栏

没有被 Screen 直接注册的深层组件,用 Hook 拿导航对象:

import { useNavigation, useRoute } from '@react-navigation/native';

function ShareButton() {
  const navigation = useNavigation();
  return <Button title="去设置" onPress={() => navigation.navigate('Settings')} />;
}

导航栏的每个部分都能自定义:

<Stack.Screen
  name="Detail"
  component={DetailScreen}
  options={{
    title: '商品详情',
    headerRight: () => <Button title="分享" onPress={() => {}} />,
    headerTintColor: '#fff',                 // 标题和返回按钮颜色
    headerStyle: { backgroundColor: '#007bff' }, // 导航栏背景
    headerShown: true,                       // false 则完全隐藏,自己画
  }}
/>

步骤 7:深链接——外部 URL 直达内页

用户点了短信里的 myapp://product/42,希望直接打开商品 42 的详情页:

const linking = {
  prefixes: ['myapp://', 'https://myapp.example.com'],
  config: {
    screens: {
      HomeTab: { screens: { Detail: 'product/:id' } },  // 嵌套路径按导航层级写
      ProfileTab: 'profile',
    },
  },
};

<NavigationContainer linking={linking}>...</NavigationContainer>

URL 里的 :id 会自动变成 route.params.id。这正是「params 必须可序列化」的原因——深链接进来时,params 是从 URL 字符串解析出来的。

讲清楚要点

  1. NavigationContainer 是唯一根:整个 App 只能有一个,包在最外层,深链接配置也挂在它身上。
  2. navigate vs push:navigate 对已在栈中的页面会复用旧实例,push 永远新开一层。详情跳详情必须用 push。
  3. 参数必须可序列化:route.params 里别传函数、类实例、Date 对象。深链接和状态恢复都要求 params 能 JSON 化,回调类需求用全局状态或事件。
  4. native-stack vs stack:native-stack 映射到系统的 UINavigationController / Fragment,转场原生、性能好;@react-navigation/stack 是纯 JS 实现,自定义上限高。默认用 native-stack。
  5. gesture-handler 是底座:iOS 右滑返回、抽屉手势、卡片转场全建立在它之上,手势识别跑在原生线程所以丝滑。它基本不用你直接写代码,但必须装、必须在入口正确引入。
  6. Tab 内页面想跳别的 Tab 的内页:支持嵌套寻址 navigation.navigate('ProfileTab', { screen: 'Settings' })。

你可能会懵的地方

  • 为什么我的页面拿不到 navigation? 只有被 Stack.Screen / Tab.Screen 直接注册的组件才自动注入 props。它内部的子组件要用 useNavigation() / useRoute() Hook 获取。

  • Tab 页面跳详情时想隐藏底部 Tab 栏? 首选方案:把详情页放到外层 Stack(Tab 的上一层),它天然盖住 Tab 栏。次选:按路由动态设 tabBarStyle: { display: 'none' }。

  • goBack 不生效? 当前已经在栈底时无处可退。先用 navigation.canGoBack() 判断,不能退就给个替代动作(比如跳首页)。

  • 返回时想传一个回调函数给上一页? 别传函数(违反可序列化原则)。用步骤 5 的 params + merge 回传数据,或用 Context / 事件发射器。

  • 页面从栈里退出后,它的 state 还在吗? 不在了。出栈即卸载,state 全部销毁。需要保留的(草稿、滚动位置)要自己存到上一层或全局。

错法对照

错法问题正解
忘记包 NavigationContainer白屏崩溃最外层包一个且仅一个
params 里传函数回调深链接/状态恢复时报错传可序列化数据,回通用 params merge 或全局状态
详情页用 navigate 跳详情页复用旧实例,数据不更新用 push 压新层
子组件层层转发 navigation props中间组件全是样板代码深层用 useNavigation / useRoute
没装 react-native-screens安卓转场异常或白屏按官方清单装齐依赖
隐藏导航栏后没留返回入口用户被困死在页面自绘返回按钮,且别禁用 gestureEnabled
回传数据后没消费标记每次回到列表页都重复刷新用完 setParams 清掉标记
手写硬件返回键监听和导航打架返回行为混乱优先用导航自带的 beforeRemove

卡点急救

  1. 跳转白屏无报错:检查 Screen 的 name 与 navigate 的字符串是否完全一致(区分大小写);确认依赖装齐后 npx expo start -c 清缓存重启。
  2. iOS 返回手势失效:多半自定义 header 时误设了 gestureEnabled: false,或手势被全屏自定义组件吃掉,逐层排查。
  3. Tab 切换页面重挂载丢状态:Tab.Navigator 设 lazy: false 让页面常驻;或把要保留的状态提升到 Context / 全局。
  4. 嵌套 navigate 报了「找不到 screen」:嵌套寻址的 name 路径要和导航层级严格对应,用 navigation.getState() 打印当前路由树排查。

面试常问

  • React Navigation 的 Stack 原理? 内部维护一个路由栈数组(state),navigate/push 入栈、goBack 出栈,UI 由这个 state 驱动渲染。native-stack 变体把这个栈映射到 iOS 的 UINavigationController 和 Android 的 Fragment 事务,所以转场动画、返回手势都是系统原生的,不是 JS 模拟的。

  • 页面间通信有哪些方式?怎么选? 四种:route.params 正向传参(跳转时带数据)、navigate + merge 回传(编辑完成通知上一页)、Context / Zustand 全局状态(多页面共享的数据,如登录用户)、事件总线(解耦的广播通知)。简单父子页面用 params,跨分支共享用全局状态。

  • 深链接(Deep Linking)怎么实现? 在 NavigationContainer 配 linking 字段:prefixes 声明 scheme/域名,config.screens 把 URL 路径映射到嵌套路由和参数。系统层 iOS 配 associated domains(Universal Links)、Android 配 intent-filter(App Links),Expo 里在 app.json 声明 scheme 即可开始联调。这也是 params 必须可序列化的根本原因。

自测三问

  1. navigate 和 push 的区别是什么?什么场景必须用 push?
  2. 为什么 route.params 不能传函数?想回调上一页该怎么办?
  3. 画出一个「首页/我的」双 Tab、首页内含列表和详情两层的导航结构树。

小结一句话

导航就是管理一个页面栈:Tab 定骨架、Stack 管层级、params 传参数、linking 接外部入口,再配上原生手势底座,App 的骨架就立起来了。

延伸

  • React Navigation 官方文档
  • 下一课:State 与 JSX——RN 里的数据流和状态管理有什么不一样。
加载本节练习…
← 上一课平台差异下一课 →State 与 JSX