本节摘要:页面导航决定用户如何在界面之间切换。RN 用社区库 React Navigation,支持栈式、标签、抽屉三种导航器;Flutter 用内置 Navigator 与路由机制。本节讲清两种导航形态(栈式与标签式)的心智模型、双框架的导航实现与页面传参方法,并对比两者的配置差异。核心心智:栈式管深度、标签管宽度。
阅读完本节,你应当能够:
一个单页面的应用几乎不存在——真实应用都有"列表点进去看详情、底部切换模块"这类多页面结构。导航要解决的,就是"页面之间怎么跳、怎么返回、参数怎么带"。
先建立两种导航形态的心智模型,它们覆盖了绝大多数 App 的导航需求:
栈式导航像一摞盘子。进入新页面 = 往上放一个盘子,返回 = 拿走顶上的盘子。适合"A 列表 → B 详情 → C 编辑"这类线性流程。iOS 的 push 返回手势、Android 的系统返回键,都对应栈的弹出操作。
标签导航像文件柜的多个抽屉。每个抽屉是一个独立模块,点标签切换,互不堆叠。适合"首页、消息、我的"这类平级模块。
一句话点题:栈式管"深度",标签管"宽度"。深度用栈(逐层深入),宽度用标签(平级切换)。
React Navigation 是 RN 社区最流行的导航库。核心概念有三:Navigator 管理一组屏幕,Screen 代表一个页面,每个屏幕组件拿到 navigation prop 执行跳转。
栈式导航:
import { createNativeStackNavigator } from '@react-navigation/native-stack'; const Stack = createNativeStackNavigator(); function AppNavigator() { return ( <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Navigator> ); }
标签导航:
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; const Tab = createBottomTabNavigator(); function MainTabs() { return ( <Tab.Navigator> <Tab.Screen name="Home" component={HomeScreen} /> <Tab.Screen name="Settings" component={SettingsScreen} /> </Tab.Navigator> ); }
跳转操作:
navigation.navigate('Details', { id: 42 }); // 跳转并传参 navigation.push('Details', { id: 43 }); // 压入新实例 navigation.goBack(); // 返回
navigate 与 push 的区别值得记住:navigate 如果目标已在栈中会复用它,push 总是创建新实例。需要"重复进入同一页面"时用 push。
栈式与标签之外,抽屉导航是第三种常见形态。它从屏幕边缘滑出侧边菜单,适合"导航项多、不想占屏幕空间"的应用。RN 用 createDrawerNavigator,Flutter 用 Scaffold 的 drawer 属性。抽屉通常用于设置、关于、退出登录这类低频但重要的入口。三种导航形态可以组合使用:标签导航管理模块,模块内部用栈式导航管页面深度,需要时再用抽屉收纳次要入口。
把三种形态与它们的组合关系画成一张图,导航体系的骨架就一目了然:

这张图是导航设计的速查卡:三种形态各司其职,组合使用构成完整导航体系。
Flutter 的 Navigator 是框架内置能力,开箱即用。基础跳转:
Navigator.push( context, MaterialPageRoute(builder: (context) => const DetailsScreen()), );
返回用 Navigator.pop(context)。命名路由适合管理较多页面的应用:
MaterialApp( initialRoute: '/', routes: { '/': (context) => const HomeScreen(), '/details': (context) => const DetailsScreen(), }, );
跳转传参通过构造函数传入:
Navigator.push( context, MaterialPageRoute( builder: (context) => DetailsScreen(id: 42), ), );
真实场景里经常需要"B 页操作完,回传结果给 A 页"。双框架的解法对应着各自的状态流动方式。
RN 的做法是传回调:A 页 navigate 时给 B 页一个参数函数,B 页完成后调用它:
navigation.navigate('Edit', { onSave: (text) => updateTodo(text), });
Flutter 的做法是 await 返回值:push 返回一个 Future,pop 时把结果传回去:
final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const EditScreen()), ); // result 就是 B 页 pop 回来的值
两种方式对应着两个框架的异步心智:RN 的回调式、Flutter 的 Future 式。理解"回传"这个需求,比记 API 更有价值——它在导航、状态、异步三个主题的交汇处,是真正的综合题。
导航状态本身也是一种应用状态。RN 的 navigation prop、Flutter 的 Navigator 都维护着"当前页面栈"这个隐式状态。它与第 5 章的状态管理结合最典型的是:页面间传参——A 页跳 B 页带参数,B 页回传结果给 A 页。这本质是"状态跨页面流动",用好了,页面间协作才顺畅。
| 维度 | React Native | Flutter |
|---|---|---|
| 方案来源 | 社区库 React Navigation | 框架内置 Navigator |
| 安装配置 | 需安装依赖并包裹容器 | 开箱即用 |
| 栈式导航 | createNativeStackNavigator | Navigator.push |
| 标签导航 | createBottomTabNavigator | BottomNavigationBar |
| 页面传参 | navigate 第二参数 | 构造函数传入 |
| 返回操作 | navigation.goBack | Navigator.pop |
坑一:忘记包裹 NavigationContainer。RN 的导航器必须包在容器里,否则报错。
坑二:导航到不存在的路由。Flutter 命名路由跳转时路由名拼错,运行时报错。保持路由名集中管理可减少此类错误。
坑三:在页面组件外拿 navigation。RN 的 navigation prop 只在屏幕组件里直接可用,组件树深处的子组件要用 useNavigation Hook。
⚠️ 常见坑(RN):navigate 与 push 混用导致页面栈混乱。理解差异:需要"返回时回到之前页面"用 navigate,需要"反复进入同一页面"用 push。栈的行为决定了返回体验。
⚠️ 常见坑(Flutter):在异步回调里用 mounted 检查后再 Navigator.pop。页面可能已弹出,再 pop 会报错。回调前先判断当前 State 是否还在树上。
💡 关键直觉:导航设计要站在用户角度想"返回会去哪"。栈式导航里每个跳转都决定了一次返回行为,设计时先画一遍"页面栈的进进出出",比直接写代码稳得多。
给待办应用加两个页面:列表页(待办列表)与详情页(点击待办查看详情)。用栈式导航实现跳转与传参,再在底部加一个"设置"标签页。这个练习把本节所有概念串起来:栈式、标签式、传参、返回。做完后你的待办应用就有了"多页面"的骨架,为 6.2 节联网后的完整应用打底。
问:深链接(Deep Link)是什么?要不要学? 深链接是"从应用外部打开指定页面"的机制——比如收到推送点一下直接进到商品详情页。它涉及导航与系统层面的配合,是进阶话题。先掌握栈式与标签式导航,深链接等到真有"外部唤醒"需求时再学,不必一开始就啃。
问:页面多了,导航文件怎么组织才不乱? 把导航器独立成文件管理。RN 用根导航器文件统一注册所有 Screen;Flutter 把路由表集中在 MaterialApp 的配置里。经验法则:新页面必须在导航配置里注册,注册表就是应用的"页面地图"。维护好这张地图,页面再多也不乱。
问:Android 返回键和 iOS 返回手势行为一致吗? 不完全一致,但导航库会尽量统一。RN 的 React Navigation 与 Flutter 的 Navigator 都处理了系统返回的适配。需要平台差异时(比如 Android 双击返回退出应用),用 Platform 判断写少量平台代码即可。多数场景下,导航库已经把差异抹平了。
问:页面跳转时怎么传复杂对象? 传引用即可,但要注意序列化边界。RN 与 Flutter 在页面跳转时通常传递对象引用(同进程内),可以直接传对象;只有涉及持久化或跨进程(深链接)时才需要序列化成可传输的格式。简单场景传 id 等标量最稳妥,需要详情数据时用 id 再查一次,反而更清晰。
导航与第 5 章的状态管理有一个重要的交汇点:导航参数与会话状态。页面跳转时传的参数(比如商品 id)属于"临时导航数据",适合随导航传递;而用户登录信息这类"贯穿全程的数据"属于全局状态,应该放在状态库里。分清这两类,就不会出现"把用户信息当导航参数到处传"的糟糕设计。判断标准:数据只被当前跳转流程使用,放导航参数;被多个无关页面长期使用,放全局状态。这个分界在真实项目里反复出现,值得现在就建立。
导航还有几个细节值得提前知道。第一,页面状态在返回时会保留吗?RN 与 Flutter 的栈式导航默认保留页面实例,返回时状态还在;但标签导航切换模块时,未激活的页面可能被重建或保留,行为因配置而异。第二,导航参数的更新——同一个页面被带不同参数再次进入时,RN 用 useEffect 监听参数变化、Flutter 用 didUpdateWidget,都用于"参数变了要刷新内容"。第三,深层页面直接返回——用户可能从最深层连续返回多次,导航库都有对应的 popToTop 或返回根路由的方法。这些细节不会在第一个导航 Demo 里遇到,但多页面应用做深了就绕不开。
多页面应用的导航架构值得提前分层设计,而不是边写边加。通常分三层:根导航器(决定整个 App 的导航骨架,标签导航还是栈导航)、模块导航(每个模块内部的页面栈)、业务页面(具体的 Screen)。用导航库时,这三层各有对应的组织方式——RN 用嵌套导航器实现,Flutter 用嵌套 Navigator 或路由分组。分层的好处是:新增页面时知道该放进哪层,模块之间互不干扰。入门项目也许只有一层,但一开始就按分层的思路组织,后面扩展会顺畅得多。
导航学完,用一组问题自查:能说清栈式与标签式各自的适用场景吗?能在 RN 与 Flutter 里各写出一个跳转并传参的最小例子吗?知道 navigate 与 push 的区别吗?能说清"导航参数与全局状态"的分界吗?前两个问题检验"会用",后两个检验"懂原理"。四个都能答上,导航这一节就真正过关了。如果还有含糊,回到对应小节再读一遍——导航是"多页面应用"的骨架,这块不牢,后面的功能都是散件。
补充一句关于导航与用户体验的联系:导航不只是技术实现,它直接塑造用户体验。栈式导航让用户知道"我可以返回",标签导航让用户知道"我在哪个模块",良好的导航设计让用户始终清楚自己身处何地、能去哪里。写导航代码时多站在用户视角思考"下一步想去哪、怎么回来",导航体验就会自然变好。这也是为什么导航常被看作"应用的信息架构"——它决定了用户如何理解和使用你的应用。
页面能跳了,下一步让页面有内容——网络请求,从 API 拉真实数据进来。