RN 中创建并操作一个页面的流程

在 iOS 中,我们用 UIViewControllerUINavigationController 来管理页面,而在 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

  1. 安装依赖
    npm install @react-navigation/native @react-navigation/native-stack
    npm install react-native-screens react-native-safe-area-context
    
  2. 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 既可以是一个静态对象,也可以是一个能访问到 navigationroute 的函数,从而实现动态配置。

a. 在 App.js 中静态配置:
Stack.Screenoptions 属性中,可以设置标题、左右按钮等。

// 在 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 页面时,将一个回调函数作为参数传给新页面。

    1. HomeScreen.js 中,修改跳转逻辑
      const navigateToDetail = () => {
        navigation.navigate('Detail', { 
          product: productData,
          onGoBack: (updatedData) => {
            console.log('从详情页返回的数据:', updatedData);
            // 在这里更新主页面的数据
          }
        });
      };
      
    2. 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。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容