在 iOS 中,我们用 UIViewController 和 UINavigationController 来管理页面,而在 React Native(结合 React Navigation)中,对应的概念则是组件(Component)和导航器(Navigator)。
下面,我将按照你的要求,逐一用代码和逻辑为你详细说明。
1. 核心概念速览
在深入代码前,先建立概念映射,能帮你更快理解:
-
页面 = 组件(Component):在RN中,所有“页面”本质上都是一个返回JSX的JavaScript函数或类。例如,
DetailScreen就是一个函数组件。 -
导航栈 = 导航器(Navigator):负责管理页面的入栈(Push)和出栈(Pop),其中最常用的是
createNativeStackNavigator,它在iOS上底层使用的就是UINavigationController。 -
导航控制器 = navigation Prop:所有被导航器管理的“页面组件”,其
props中都会自动注入一个名为navigation的对象,通过它的方法(如.navigate()或.goBack())来实现页面切换。
2. 前期准备:环境配置与基础导航栈
首先,需要安装核心库并创建一个导航容器,这类似于在iOS中设置一个 UINavigationController 作为 rootViewController。
-
安装依赖:
npm install @react-navigation/native @react-navigation/native-stack npm install react-native-screens react-native-safe-area-context -
在
App.js中配置导航器:import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import HomeScreen from './screens/HomeScreen'; import DetailScreen from './screens/DetailScreen'; const Stack = createNativeStackNavigator(); function App() { return ( <NavigationContainer> <Stack.Navigator initialRouteName="Home"> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Detail" component={DetailScreen} /> </Stack.Navigator> </NavigationContainer> ); } export default App;-
逻辑说明:
NavigationContainer是管理导航状态的根容器。Stack.Navigator则定义了栈导航器,其中的每个Stack.Screen都代表一个页面组件。
-
逻辑说明:
3. 创建一个新页面(Controller)
在 React Native 中,我们通常会为每个页面创建一个独立的文件,它就是一个标准的组件。
创建 screens/DetailScreen.js 文件,定义一个函数组件,它接受 { navigation, route } 作为参数,并返回需要显示的界面。
// screens/DetailScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Image, TouchableOpacity, ScrollView, Alert } from 'react-native';
// 接收 navigation 和 route 作为 props
const DetailScreen = ({ navigation, route }) => {
// 1. 定义页面的内部状态
const [isLiked, setIsLiked] = useState(false);
// 2. 获取从上一个页面传递过来的参数
// 假设传递的参数名为 product
const { product } = route.params || {};
// 3. 页面内的数据处理函数
const handleLikePress = () => {
setIsLiked(!isLiked);
Alert.alert("提示", `你${!isLiked ? '点赞了' : '取消了点赞'}商品: ${product?.name || ''}`);
};
return (
<ScrollView style={styles.container}>
{/* 模拟商品图片 */}
<View style={styles.imagePlaceholder}>
<Text style={styles.placeholderText}>商品主图区域</Text>
</View>
<View style={styles.infoContainer}>
<Text style={styles.productName}>{product?.name || '默认商品名称'}</Text>
<Text style={styles.productPrice}>¥ {product?.price || '0.00'}</Text>
<Text style={styles.productDesc}>
这是商品的详细描述内容。这里展示了如何在一个RN页面中组织和渲染自定义视图,类似于iOS中在ViewController的view上添加subviews。
</Text>
</View>
{/* 自定义按钮,模拟底部操作栏 */}
<TouchableOpacity style={styles.likeButton} onPress={handleLikePress}>
<Text style={styles.buttonText}>{isLiked ? '❤️ 已点赞' : '🤍 点赞'}</Text>
</TouchableOpacity>
</ScrollView>
);
};
// 样式定义,类似于iOS中的AutoLayout或Frame布局
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
imagePlaceholder: {
height: 300,
backgroundColor: '#E0E0E0',
justifyContent: 'center',
alignItems: 'center',
},
placeholderText: {
color: '#888',
},
infoContainer: {
padding: 16,
backgroundColor: '#FFF',
marginTop: 8,
},
productName: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 8,
},
productPrice: {
fontSize: 24,
color: '#FF3B30',
fontWeight: '600',
marginBottom: 12,
},
productDesc: {
fontSize: 14,
color: '#666',
lineHeight: 20,
},
likeButton: {
margin: 16,
backgroundColor: '#007AFF',
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: '#FFF',
fontSize: 16,
fontWeight: '600',
},
});
export default DetailScreen;
4. 导航栏配置
RN的导航栏配置非常灵活,可以通过 options 属性来设置。options 既可以是一个静态对象,也可以是一个能访问到 navigation 和 route 的函数,从而实现动态配置。
a. 在 App.js 中静态配置:
在 Stack.Screen 的 options 属性中,可以设置标题、左右按钮等。
// 在 App.js 中为 DetailScreen 配置导航栏
<Stack.Screen
name="Detail"
component={DetailScreen}
options={{
title: '商品详情', // 设置标题
headerStyle: { backgroundColor: '#FFF' }, // 设置导航栏背景色
headerTintColor: '#007AFF', // 设置返回按钮和标题颜色
headerTitleStyle: { fontWeight: 'bold' }, // 设置标题样式
}}
/>
b. 在 DetailScreen.js 中动态配置:
也可以在页面组件内部,通过 useEffect 钩子调用 navigation.setOptions 方法来动态设置,这在需要根据数据或状态来调整标题或按钮时非常有用。
// 在 DetailScreen 组件内部
useEffect(() => {
navigation.setOptions({
// 动态设置右侧分享按钮
headerRight: () => (
<TouchableOpacity onPress={() => Alert.alert('分享', '分享商品链接')} style={{ marginRight: 15 }}>
<Text style={{ color: '#007AFF', fontSize: 16 }}>分享</Text>
</TouchableOpacity>
),
// 根据商品名动态设置标题
title: product?.name || '商品详情',
});
}, [navigation, product]);
5. 页面跳转与参数传递
接下来,我们在主页(HomeScreen)中实现点击按钮,Push新页面并传参。
创建 screens/HomeScreen.js 文件:
// screens/HomeScreen.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
const HomeScreen = ({ navigation }) => {
// 模拟的商品数据
const productData = {
id: '001',
name: 'React Native 实战宝典',
price: '99.00',
};
const navigateToDetail = () => {
// Push新页面,并携带参数 product
// 第二个参数就是需要传递的数据对象
navigation.navigate('Detail', { product: productData });
// 也可以使用 navigation.push('Detail', { product: productData });
// navigate 和 push 在大多数场景下效果相同,区别在于 navigate 会尝试返回已存在的页面,而 push 总会创建新页面
};
return (
<View style={styles.container}>
<Text style={styles.title}>商品列表</Text>
<TouchableOpacity style={styles.button} onPress={navigateToDetail}>
<Text style={styles.buttonText}>查看商品详情 (Push)</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 30,
},
button: {
backgroundColor: '#007AFF',
paddingVertical: 12,
paddingHorizontal: 30,
borderRadius: 8,
},
buttonText: {
color: '#FFF',
fontSize: 16,
},
});
export default HomeScreen;
在刚刚创建的详情页(DetailScreen)中,通过 route.params 接收参数,这部分已经在之前示例的注释中体现。
6. 生命周期与事件监听
在RN中,组件的生命周期(如 componentDidMount)不等于屏幕的生命周期,因为页面在导航栈中可能被缓存而不被卸载。useFocusEffect 是处理这类需求的推荐方式。
-
useFocusEffect:当页面获得焦点(即成为当前可见页面)时触发,非常适合用于数据刷新、埋点上报等。它要求传入一个用React.useCallback包裹的回调。
// 在 DetailScreen.js 中使用
import { useFocusEffect } from '@react-navigation/native';
// ... 在 DetailScreen 组件内部,与 useState 等并列
useFocusEffect(
React.useCallback(() => {
// 页面进入时执行,相当于 viewDidAppear
console.log('✅ 商品详情页获得焦点');
// 可以在这里发起网络请求,刷新页面数据
// fetchProductDetail(product.id);
// 可选:返回一个清理函数,在页面失去焦点时执行
return () => {
console.log('❌ 商品详情页失去焦点');
// 可以在这里暂停视频播放、停止轮询等操作
};
}, [product?.id]) // 依赖项变化时,回调会重新创建
);
7. 如何Pop页面
返回上一页非常直接,在 DetailScreen 的任意位置调用 navigation.goBack() 即可。
// 在 DetailScreen 的任意位置
const handleBackPress = () => {
// 执行返回操作
navigation.goBack();
// 如果你需要返回时携带一些信息给上一页,
// 可以通过路由参数传递一个回调函数,在返回前调用,详见下面补充示例
};
-
补充:返回并传值
这需要在push页面时,将一个回调函数作为参数传给新页面。-
在
HomeScreen.js中,修改跳转逻辑:const navigateToDetail = () => { navigation.navigate('Detail', { product: productData, onGoBack: (updatedData) => { console.log('从详情页返回的数据:', updatedData); // 在这里更新主页面的数据 } }); }; -
在
DetailScreen.js中,返回前调用回调:const handleBackWithData = () => { const { onGoBack } = route.params || {}; if (onGoBack) { onGoBack({ success: true, message: '操作已完成' }); } navigation.goBack(); };
-
在
总结对比
| 概念/操作 | iOS 原生 (UIKit) | React Native (React Navigation) |
|---|---|---|
| 新页面创建 | 创建 UIViewController 子类 |
创建新的 组件 (Component) 文件 |
| 页面跳转(Push) | self.navigationController pushViewController: |
navigation.navigate() 或 navigation.push()
|
| 参数传递 | 在 prepareForSegue: 中设置属性,或初始化时赋值 |
navigation.navigate('RouteName', { /* 参数对象 */ }) |
| 接收参数 | 在 viewDidLoad 或属性中直接使用 |
route.params 对象 |
| 返回页面(Pop) | self.navigationController popViewControllerAnimated: |
navigation.goBack() |
| 导航栏配置 | 在 viewDidLoad 中配置 navigationItem 等 |
静态 options 属性或动态 navigation.setOptions
|
| 生命周期 |
viewDidAppear, viewDidDisappear 等方法 |
useFocusEffect 钩子,监控焦点变化 |
| 页面未卸载监听 | 通过 delegate 或通知中心 |
navigation.addListener('focus') 或 useFocusEffect
|
通过这份对比和示例,你应该能快速建立起对 React Native 导航系统的认知。它的核心理念与原生开发高度相似,只是表达方式从 Objective-C/Swift 的类和方法,转变为了 JavaScript 的组件和 Hook。