ReactNative-2026

40 阅读17分钟

搭建开发环境

reactnative.cn/docs/enviro…

  • 选择 React Native CLI Quickstart
  • 开发平台 macOS
  • 目标平台 Android
  • 其他按上述链接步骤安装,注意node 请检查其版本是否在 22.11.0 以上
mac安装Homebrew

brew.sh/

Node & Watchman
brew install node@22  
brew install watchman
npm install -g yarn
Java Development Kit
brew install --cask zulu@17

# 获得 JDK 安装程序的路径
brew info --cask zulu@17

# ==> zulu@17: <版本号>
# https://www.azul.com/downloads/
# Installed
# /opt/homebrew/Caskroom/zulu@17/<版本号> (185.8MB) (注意在 Intel 芯片的 Mac 上,路径可能是 /usr/local/Caskroom/zulu@17/<版本号>)
# Installed using the formulae.brew.sh API on 2024-06-06 at 10:00:00

# 导航到上面打印出来的路径
open /opt/homebrew/Caskroom/zulu@17/<版本号>
# 或者可能是 /usr/local/Caskroom/zulu@17/<版本号>

打开 Finder,双击 Double-Click to Install Azul Zulu JDK 17.pkg 包来安装 JDK。 安装 JDK 后,请更新 JAVA_HOME 环境变量。

配置Android 开发环境
1. 安装 Android Studio

首先下载和安装 Android Studio, 安装界面中选择"Custom"选项,确保选中了以下几项:

  • Android SDK
  • Android SDK Platform
  • Android Virtual Device
2. 安装 Android SDK

你可以在 Android Studio 的欢迎界面中找到 SDK Manager。点击"Configure",然后就能看到"SDK Manager"。

SDK Manager 还可以在 Android Studio 的"Preferences"菜单中找到。具体路径是Appearance & Behavior → System Settings → Android SDK

3. 配置ANDROID_HOME 环境变量
  • 打开 macOS 环境配置文件 nano ~/.zshrc
export ANDROID_HOME=$HOME/Library/Android/sdk

export PATH=$PATH:$ANDROID_HOME/emulator

export PATH=$PATH:$ANDROID_HOME/platform-tools
  • 环境变量设置立即生效 source ~/.zshrc
创建新项目
npx @react-native-community/cli init 项目名
准备 Android 设备
使用 Android 真机

你也可以使用 Android 真机来代替模拟器进行开发,只需用 usb 数据线连接到电脑,然后遵照在设备上运行这篇文档的说明操作即可。 只需用 usb 数据线连接到电脑, 提示手机授权后,打开开发者调试.

使用 Android 模拟器

你可以使用 Android Studio 打开项目下的"android"目录,然后可以使用"AVD Manager"来查看可用的虚拟设备。如果你刚刚才安装 Android Studio,那么可能需要先创建一个虚拟设备。点击"Create Virtual Device...",然后选择所需的设备类型并点击"Next".

编译并运行 React Native 应用

确保你先运行了模拟器或者连接了真机,然后在你的项目目录中运行yarn android

使用adb devices命令 检查你的设备是否能正确连接
  • 若以下指令没有显示手机设备,说明数据线没有连接设置成功,后面的手机调试就不会成功
  • emulator-xxx Android Studio软件开启的虚拟设备, 不带emulator表示物理设备
  • offline 表示虚拟或物理设备离线 device 表示当前设备处于激活有效状态
$ adb devices
List of devices attached
emulator-5554 device    # 模拟器
14ed2fcc device         # usb数据线开发者打开后显示的手机设备
  • 显示以上 模拟器 + 手机设备后,启动项目,
npm run start
npm run android

注意:如果npm run android 报错 可能是上面的虚拟设备或者物理设备都没有激活。再次检测Android Studio软件开启的虚拟设备 或 数据线连接的手机设备是否允许传输文件等。另外项目启动失败,注意检测node版本,也可以全局设置node默认版本。

nvm use 22
nvm alias default 22
  • 手机就会有显示上述脚手架创建的App,每次更改项目代码,手机上的App会自动刷新
   r  - reload app(s)  // 加载刷新
   d  - open Dev Menu  // 开启开发辅助工具 开启fast refresh
   j  - open DevTools // 电脑开启日志控台  就可以看到console.log日志了

RN入门基础

以下示例对应的基础包版本信息如下

 "react": "19.2.3",
 "react-native": "0.84.1",
 
 "engines": {
    "node": ">= 22.11.0"
  }
React 基础

React Native 的基础是React, React 的核心概念:

import React, { useState } from "react";
import { Button, Text, View } from "react-native";

const Cat = (props) => {
  const [isHungry, setIsHungry] = useState(true);

  return (
    <View>
      <Text>
        I am {props.name}, and I am {isHungry ? "hungry" : "full"}!
      </Text>
      <Button
        onPress={() => {
          setIsHungry(false);
        }}
        disabled={!isHungry}
        title={isHungry ? "Pour me some milk, please!" : "Thank you!"}
      />
    </View>
  );
}

const Cafe = () => {
  return (
    <>
      <Cat name="Munkustrap" />
      <Cat name="Spot" />
    </>
  );
}

export default Cafe;
RN常用基础组件
  • View 搭建用户界面的最基础组件。容器组件 类似web的div
  • Text 显示文本内容的组件。文本显示组件 类似web的p
  • Image 图片显示组件 source图片源
import {Image, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
  tinyLogo: {
    width: 50,
    height: 50,
  },
});

<Image
    style={styles.tinyLogo}
    source={{
      uri: 'https://reactnative.dev/img/tiny_logo.png',
    }}
/>
  • TextInput 文本输入框。 类似web的input
import React from 'react';
import {StyleSheet, TextInput} from 'react-native';
import {SafeAreaView, SafeAreaProvider} from 'react-native-safe-area-context';

const TextInputExample = () => {
  const [text, onChangeText] = React.useState('Useless Text');
  const [number, onChangeNumber] = React.useState('');

  return (
    <SafeAreaProvider>
      <SafeAreaView>
        <TextInput
          style={styles.input}
          onChangeText={onChangeText}
          value={text}
        />
        <TextInput
          style={styles.input}
          onChangeText={onChangeNumber}
          value={number}
          placeholder="useless placeholder"
          keyboardType="numeric"
        />
      </SafeAreaView>
    </SafeAreaProvider>
  );
};

const styles = StyleSheet.create({
  input: {
    height: 40,
    margin: 12,
    borderWidth: 1,
    padding: 10,
  },
});

export default TextInputExample;
  • ScrollView 可滚动的容器视图。
import React from 'react';
import {
  StyleSheet,
  Text,
  SafeAreaView,
  ScrollView,
  StatusBar,
} from 'react-native';

const App = () => {
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        <Text style={styles.text}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
          minim veniam, quis nostrud exercitation ullamco laboris nisi ut
          aliquip ex ea commodo consequat. Duis aute irure dolor in
          reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
          pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
          culpa qui officia deserunt mollit anim id est laborum.
        </Text>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: StatusBar.currentHeight,
  },
  scrollView: {
    backgroundColor: 'pink',
    marginHorizontal: 20,
  },
  text: {
    fontSize: 42,
  },
});

export default App;
  • FlatList 高性能的简单列表组件
    • 支持单独的头部组件。
    • 支持单独的尾部组件。
    • 支持自定义行间分隔线。
    • 支持下拉刷新。
    • 支持上拉加载。
  • Button 安卓和ios样式不同
<Button
  onPress={onPressLearnMore}
  title="Learn More"
  color="#841584"
  accessibilityLabel="Learn more about this purple button"
/>
  • Pressable 是一个核心组件的封装, 常用来封装按钮组件
<Pressable onPress={onPressFunction}>
  <Text>I'm pressable!</Text>
</Pressable>
  • TouchableOpacity 本组件用于封装视图,使其可以正确响应触摸操作。当按下的时候,封装的视图的不透明度会降低
import React, { useState } from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";

const App = () => {
  const [count, setCount] = useState(0);
  const onPress = () => setCount(prevCount => prevCount + 1);

  return (
    <View style={styles.container}>
      <View style={styles.countContainer}>
        <Text>Count: {count}</Text>
      </View>
      <TouchableOpacity
        style={styles.button}
        onPress={onPress}
      >
        <Text>Press Here</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    paddingHorizontal: 10
  },
  button: {
    alignItems: "center",
    backgroundColor: "#DDDDDD",
    padding: 10
  },
  countContainer: {
    alignItems: "center",
    padding: 10
  }
});

export default App;
样式
  • 所有的核心组件都接受名为style的属性,这些样式名基本上是遵循了 web 上的 CSS 的命名,只是按照 JS 的语法要求使用了驼峰命名法,例如将background-color改为backgroundColor。我们建议使用StyleSheet.create来集中定义组件的样式
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

const LotsOfStyles = () => {
    return (
      <View style={styles.container}>
        <Text style={styles.red}>just red</Text>
        <Text style={styles.bigBlue}>just bigBlue</Text>
        <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
        <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
      </View>
    );
};

const styles = StyleSheet.create({
  container: {
    marginTop: 50,
  },
  bigBlue: {
    color: 'blue',
    fontWeight: 'bold',
    fontSize: 30,
  },
  red: {
    color: 'red',
  },
});

export default LotsOfStyles;
  • React Native 中的尺寸都是无单位的

  • 使用 Flexbox 布局

    React Native 中的 Flexbox 的工作原理和 web 上的 CSS 基本一致,当然也存在少许差异。首先是默认值不同:flexDirection的默认值为column(而不是row),alignContent默认值为 flex-start(而不是 stretch), flexShrink 默认值为0 (而不是1), 而flex只能指定一个数字值。

    组件能够撑满剩余空间的前提是其父容器的尺寸不为零。如果父容器既没有固定的widthheight,也没有设定flex,则父容器的尺寸为零。其子组件如果使用了flex,也是无法显示的。

第三方组件

除了简单的npm i xxx下载之外,必须查看githubnpm文档的使用说明,可能需要在项目androidios目录添加配置变更等,不然组件无法使用

geolocation 获取位置 '@react-native-community/geolocation'

www.npmjs.com/package/@re…

    1. 安装包 yarn add @react-native-community/geolocation
  "@react-native-community/geolocation": "^3.4.0",
    1. 安卓设备必须配置 [ios设备需要参照文档操作]
// 在 `AndroidManifest.xml`文件 添加以下
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 // 或者
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  • 3. 由于 '@react-native-community/geolocation' 已经从 从 React Native 核心模块迁移。所以需要如下代码
import Geolocation from '@react-native-community/geolocation';

Geolocation.setRNConfiguration(config);
    1. 示例
import React, { useCallback, useState, useEffect } from 'react';
import Geolocation from '@react-native-community/geolocation';
import { View, Alert, Platform, Text, Linking, Button } from 'react-native';
import {
 request,
 PERMISSIONS,
 RESULTS,
 openSettings,
} from 'react-native-permissions';

const GeoLocationExample = () => {
 const [locationData, setLocationData] = useState({});

 useEffect(() => {
   // 在应用启动时配置 Geolocation 模块
   configureGeolocation();
 }, []);

 const configureGeolocation = () => {
   Geolocation.setRNConfiguration({
     // skipPermissionRequests: false, // 如果你会自己处理权限请求,可以设为 true
     locationProvider: 'playServices', // 🔴 关键配置:强制使用 Google Play 服务定位 API
     // enableBackgroundLocationUpdates: true, // 如果需要后台定位可以开启
   });
   console.log('Geolocation configured to use playServices');
 };

 // 请求位置权限
 const requestLocationPermission = useCallback(async () => {
   if (Platform.OS === 'ios') {
     const result = await request(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
     return result === RESULTS.GRANTED;
   } else {
     const result = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
     console.log('result---', result);
     return result === RESULTS.GRANTED;
   }
 }, []);

 const onGetLocation = async () => {
   const hasPermission = await requestLocationPermission();
   if (hasPermission) {
     const location = await getCurrentLocation();
     console.log('getCurrentLocation---location', location);
     setLocationData(location);
   } else {
     Alert.alert(
       '需要位置权限',
       '我们需要访问您的位置信息才能继续操作,请在设置中开启位置权限',
       [
         {
           text: '取消',
           style: 'cancel',
           onPress: () => {
             console.log('用户取消授权');
           },
         },
         {
           text: '去设置',
           onPress: () => {
             console.log('用户前往设置页面');
             // 打开应用设置页面
             if (Platform.OS === 'ios') {
               Linking.openURL('app-settings:');
             } else {
               openSettings(); // 打开安卓手机的应用权限设置界面
             }
           },
         },
       ],
       { cancelable: false },
     );
   }
 };

 // 获取位置信息
 const getCurrentLocation = useCallback(() => {
   return new Promise((resolve, reject) => {
     Geolocation.getCurrentPosition(
       position => {
         console.log('getCurrentPosition---', position);
         resolve({
           latitude: position.coords.latitude,
           longitude: position.coords.longitude,
           accuracy: position.coords.accuracy,
         });
       },
       error => {
         console.error('getCurrentPosition---error', error);
         reject(error);
       },
       { enableHighAccuracy: true, timeout: 15000 },
     );
   });
 }, []);

 return (
   <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
     <Text>locationData.latitude:{locationData.latitude}</Text>
     <Text>locationData.longitude:{locationData.longitude}</Text>
     <Button title="获取位置" onPress={onGetLocation}></Button>
   </View>
 );
};

export default GeoLocationExample;
拍照+选取图片+录视频 "react-native-image-picker"
  • 安装包
   "react-native-image-picker": "^8.2.1",
  • 安卓配置, 配置完成后就可以在手机设置里面看到该应用管理中看到对应的权限。打开 android/app/src/main/AndroidManifest.xml,在 <manifest> 标签内添加以下权限:
   <!-- 相机权限 -->
   <uses-permission android:name="android.permission.CAMERA" />

   <!-- 存储权限(如需访问文件) 兼容 Android 12 及以下版本的旧权限 -->
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

   <!-- 选择图片 Android 13+ 必需的新权限(按需添加) -->
   <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
   <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
   
   <!-- Include this only if you are planning to use the microphone for video recording -->
   <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  • 示例代码
import React, { useCallback, useState } from 'react';
import {
  View,
  Alert,
  Platform,
  Text,
  Linking,
  TouchableOpacity,
  Image,
  ScrollView,
  StyleSheet,
} from 'react-native';
import {
  request,
  PERMISSIONS,
  RESULTS,
  openSettings,
} from 'react-native-permissions';
import * as ImagePicker from 'react-native-image-picker';

const TakePhotoExample = () => {
  const [imageUri, setImageUri] = useState('');
  const [videoUri, setVideoUri] = useState('');
  const [mediaType, setMediaType] = useState(''); // 'photo' 或 'video'

  // 请求相机权限
  const requestCameraPermission = useCallback(async () => {
    if (Platform.OS === 'ios') {
      const result = await request(PERMISSIONS.IOS.CAMERA);
      return result === RESULTS.GRANTED;
    } else {
      const result = await request(PERMISSIONS.ANDROID.CAMERA);
      return result === RESULTS.GRANTED;
    }
  }, []);

  // 请求相册权限
  const requestGalleryPermission = useCallback(async () => {
    if (Platform.OS === 'ios') {
      const result = await request(PERMISSIONS.IOS.PHOTO_LIBRARY);
      return result === RESULTS.GRANTED;
    } else {
      // Android 13+ 需要新的权限
      if (Platform.Version >= 33) {
        const result = await request(PERMISSIONS.ANDROID.READ_MEDIA_IMAGES);
        return result === RESULTS.GRANTED;
      } else {
        const result = await request(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE);
        return result === RESULTS.GRANTED;
      }
    }
  }, []);

  // 显示权限提示
  const showPermissionAlert = permissionType => {
    const messages = {
      camera: {
        title: '需要相机权限',
        message: '我们需要访问您的相机权限才能继续操作,请在设置中开启相机权限',
      },
      gallery: {
        title: '需要相册权限',
        message: '我们需要访问您的相册权限才能继续操作,请在设置中开启相册权限',
      },
    };

    const msg = messages[permissionType];

    Alert.alert(
      msg.title,
      msg.message,
      [
        {
          text: '取消',
          style: 'cancel',
        },
        {
          text: '去设置',
          onPress: () => {
            if (Platform.OS === 'ios') {
              Linking.openURL('app-settings:');
            } else {
              openSettings();
            }
          },
        },
      ],
      { cancelable: false },
    );
  };

  // 拍照
  const onTakePhoto = async () => {
    const hasPermission = await requestCameraPermission();
    if (hasPermission) {
      const res = await ImagePicker.launchCamera({
        mediaType: 'photo',
        includeBase64: false,
        quality: 0.8,
        saveToPhotos: true, // 保存到相册
      });

      console.log('拍照结果:', res);

      if (res.didCancel) {
        console.log('用户取消了拍照');
      } else if (res.error) {
        console.log('拍照出错:', res.error);
        Alert.alert('拍照失败', res.error);
      } else {
        const { assets } = res;
        if (assets && assets.length > 0) {
          setImageUri(assets[0].uri);
          setMediaType('photo');
          setVideoUri(''); // 清除视频
        }
      }
    } else {
      showPermissionAlert('camera');
    }
  };

  // 录视频
  const onRecordVideo = async () => {
    const hasPermission = await requestCameraPermission();
    if (hasPermission) {
      const res = await ImagePicker.launchCamera({
        mediaType: 'video',
        videoQuality: 'high',
        durationLimit: 60, // 最长录制60秒
        includeBase64: false,
        saveToPhotos: true,
      });

      console.log('录制结果:', res);

      if (res.didCancel) {
        console.log('用户取消了录制');
      } else if (res.error) {
        console.log('录制出错:', res.error);
        Alert.alert('录制失败', res.error);
      } else {
        const { assets } = res;
        if (assets && assets.length > 0) {
          setVideoUri(assets[0].uri);
          setMediaType('video');
          setImageUri(''); // 清除图片
          console.log('视频信息:', {
            uri: assets[0].uri,
            duration: assets[0].duration,
            fileSize: assets[0].fileSize,
          });
        }
      }
    } else {
      showPermissionAlert('camera');
    }
  };

  // 从相册选择图片
  const onSelectImage = async () => {
    const hasPermission = await requestGalleryPermission();
    if (hasPermission) {
      const res = await ImagePicker.launchImageLibrary({
        mediaType: 'photo',
        includeBase64: false,
        quality: 0.8,
        selectionLimit: 1, // 限制选择数量
      });

      console.log('选择图片结果:', res);

      if (res.didCancel) {
        console.log('用户取消了选择');
      } else if (res.error) {
        console.log('选择出错:', res.error);
        Alert.alert('选择失败', res.error);
      } else {
        const { assets } = res;
        if (assets && assets.length > 0) {
          setImageUri(assets[0].uri);
          setMediaType('photo');
          setVideoUri(''); // 清除视频
        }
      }
    } else {
      showPermissionAlert('gallery');
    }
  };

  // 从相册选择视频
  const onSelectVideo = async () => {
    const hasPermission = await requestGalleryPermission();
    if (hasPermission) {
      const res = await ImagePicker.launchImageLibrary({
        mediaType: 'video',
        includeBase64: false,
        selectionLimit: 1,
      });

      console.log('选择视频结果:', res);

      if (res.didCancel) {
        console.log('用户取消了选择');
      } else if (res.error) {
        console.log('选择出错:', res.error);
        Alert.alert('选择失败', res.error);
      } else {
        const { assets } = res;
        if (assets && assets.length > 0) {
          setVideoUri(assets[0].uri);
          setMediaType('video');
          setImageUri(''); // 清除图片
          console.log('视频信息:', {
            uri: assets[0].uri,
            duration: assets[0].duration,
            fileSize: assets[0].fileSize,
          });
        }
      }
    } else {
      showPermissionAlert('gallery');
    }
  };

  // 清除当前媒体
  const onClear = () => {
    setImageUri('');
    setVideoUri('');
    setMediaType('');
  };

  return (
    <ScrollView contentContainerStyle={styles.container}>
      <Text style={styles.title}>媒体选择示例</Text>

      {/* 媒体预览区域 */}
      {mediaType === 'photo' && imageUri ? (
        <View style={styles.previewContainer}>
          <Image source={{ uri: imageUri }} style={styles.imagePreview} />
          <Text style={styles.previewText}>图片预览</Text>
          <TouchableOpacity style={styles.clearButton} onPress={onClear}>
            <Text style={styles.clearButtonText}>清除</Text>
          </TouchableOpacity>
        </View>
      ) : mediaType === 'video' && videoUri ? (
        <View style={styles.previewContainer}>
          <View style={styles.videoPlaceholder}>
            <Text style={styles.videoIcon}>🎥</Text>
            <Text style={styles.videoText}>视频已录制</Text>
            <Text style={styles.videoPath} numberOfLines={1}>
              {videoUri}
            </Text>
          </View>
          <TouchableOpacity style={styles.clearButton} onPress={onClear}>
            <Text style={styles.clearButtonText}>清除</Text>
          </TouchableOpacity>
        </View>
      ) : (
        <View style={styles.emptyPreview}>
          <Text style={styles.emptyText}>暂无媒体</Text>
        </View>
      )}

      {/* 操作按钮区域 */}
      <View style={styles.buttonGroup}>
        <Text style={styles.groupTitle}>相机操作</Text>
        <View style={styles.buttonRow}>
          <TouchableOpacity
            style={[styles.button, styles.primaryButton]}
            onPress={onTakePhoto}
          >
            <Text style={styles.buttonText}>📸 拍照</Text>
          </TouchableOpacity>

          <TouchableOpacity
            style={[styles.button, styles.successButton]}
            onPress={onRecordVideo}
          >
            <Text style={styles.buttonText}>🎥 录视频</Text>
          </TouchableOpacity>
        </View>
      </View>

      <View style={styles.buttonGroup}>
        <Text style={styles.groupTitle}>相册选择</Text>
        <View style={styles.buttonRow}>
          <TouchableOpacity
            style={[styles.button, styles.infoButton]}
            onPress={onSelectImage}
          >
            <Text style={styles.buttonText}>🖼️ 选择图片</Text>
          </TouchableOpacity>

          <TouchableOpacity
            style={[styles.button, styles.warningButton]}
            onPress={onSelectVideo}
          >
            <Text style={styles.buttonText}>🎬 选择视频</Text>
          </TouchableOpacity>
        </View>
      </View>

      {/* 显示媒体信息 */}
      {(imageUri || videoUri) && (
        <View style={styles.infoContainer}>
          <Text style={styles.infoTitle}>媒体信息:</Text>
          <Text style={styles.infoText} numberOfLines={2}>
            {mediaType === 'photo' ? imageUri : videoUri}
          </Text>
        </View>
      )}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    textAlign: 'center',
    marginBottom: 20,
  },
  previewContainer: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 15,
    marginBottom: 20,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  imagePreview: {
    width: '100%',
    height: 250,
    borderRadius: 8,
    marginBottom: 10,
  },
  videoPlaceholder: {
    width: '100%',
    height: 150,
    backgroundColor: '#1e1e2f',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 10,
  },
  videoIcon: {
    fontSize: 48,
    marginBottom: 10,
  },
  videoText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  videoPath: {
    color: '#aaa',
    fontSize: 12,
    paddingHorizontal: 10,
  },
  previewText: {
    fontSize: 14,
    color: '#666',
    marginBottom: 10,
  },
  clearButton: {
    backgroundColor: '#dc3545',
    paddingHorizontal: 20,
    paddingVertical: 8,
    borderRadius: 6,
  },
  clearButtonText: {
    color: '#fff',
    fontSize: 14,
    fontWeight: '500',
  },
  emptyPreview: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 40,
    marginBottom: 20,
    alignItems: 'center',
    borderWidth: 2,
    borderColor: '#ddd',
    borderStyle: 'dashed',
  },
  emptyText: {
    fontSize: 16,
    color: '#999',
  },
  buttonGroup: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 15,
    marginBottom: 15,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.05,
    shadowRadius: 2,
    elevation: 2,
  },
  groupTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#555',
    marginBottom: 12,
  },
  buttonRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    gap: 10,
  },
  button: {
    flex: 1,
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  primaryButton: {
    backgroundColor: '#007AFF',
  },
  successButton: {
    backgroundColor: '#34C759',
  },
  infoButton: {
    backgroundColor: '#5856D6',
  },
  warningButton: {
    backgroundColor: '#FF9500',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  infoContainer: {
    backgroundColor: '#fff',
    borderRadius: 8,
    padding: 15,
    marginTop: 10,
  },
  infoTitle: {
    fontSize: 14,
    fontWeight: '600',
    color: '#333',
    marginBottom: 5,
  },
  infoText: {
    fontSize: 12,
    color: '#666',
  },
});

export default TakePhotoExample;

下载文件 'react-native' Linking
本地存储 @react-native-async-storage/async-storage
web容器 'react-native-webview'
  • 安装包 react-native-webview
  "react-native-webview": "^13.16.1",
  • Rn WebViewScreen示例
import { useRef, useCallback, useLayoutEffect } from 'react';
import { View, StyleSheet, ActivityIndicator, Alert, Text } from 'react-native';
import { WebView } from 'react-native-webview';
import { useNavigation } from '@react-navigation/native';

const WebViewScreen = ({ route }) => {
  console.log('WebViewScreen---start');

  const navigation = useNavigation();
  const webViewRef = useRef(null);
  const { url, title } = route.params; // url: h5页面链接地址

  // 设置导航标题
  useLayoutEffect(() => {
    navigation.setOptions({ title: title || '加载中...' });
  }, [navigation, title]);

  // 处理从H5发来的消息
  const handleMessage = useCallback(async event => {
    try {
      const data = JSON.parse(event.nativeEvent.data);
      console.log('data', data);
      const { type, callbackId, params } = data;
      console.log('type', type, 'params', params);
      let result = null;
      let error = null;

      switch (type) {
        case 'getUserInfo':
          // 模拟用户信息
          result = {
            id: '123456',
            name: '测试用户',
            avatar: 'https://www.example.com/avatar.jpg',
            phone: '13800138000',
          };
          break;

        case 'getOrders':
          // 模拟订单数据
          result = [
            {
              id: '1',
              orderNo: 'ORD2024001',
              amount: 199,
              status: 'pending',
            },
            { id: '2', orderNo: 'ORD2024002', amount: 299, status: 'paid' },
          ];
          break;
        case 'getAuthToken':
          // 模拟token
          result = 'mock_token_123456';
          break;
        default:
          error = `未知方法: ${type}`;
      }

      // 将结果回调给H5
      if (webViewRef.current && callbackId) {
        const callbackScript = `
          window.dispatchEvent(new CustomEvent('RNCallback', {
            detail: ${JSON.stringify({ callbackId, result, error })}
          }));
          true;
        `;
        webViewRef.current.injectJavaScript(callbackScript);
      }
    } catch (error) {
      console.error('处理H5消息失败:', error);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // 注入到H5的JavaScript代码
  const injectedJavaScript = `
    (function() {
      // RN Bridge 对象
      window.RNBridge = {
        callbacks: {},
        
        // 通用调用方法
        call(method, params = {}) {
          return new Promise((resolve, reject) => {
            const callbackId = Date.now() + '_' + Math.random().toString(36);
            this.callbacks[callbackId] = { resolve, reject };
            
            window.ReactNativeWebView.postMessage(JSON.stringify({
              type: method,
              params,
              callbackId
            }));
          });
        },
        
        // 获取用户信息
        getUserInfo(data) {
          return this.call('getUserInfo', data);
        },
        
        // 获取订单列表
        getOrders(data) {
          return this.call('getOrders', data);
        },
        
        // 获取认证Token
        getAuthToken(data) {
          return this.call('getAuthToken', data);
        },
      };
      
      // 监听RN回调
      window.addEventListener('RNCallback', function(event) {
        const { callbackId, result, error } = event.detail;
        const callback = window.RNBridge.callbacks[callbackId];
        
        if (callback) {
          if (error) {
            callback.reject(new Error(error));
          } else {
            callback.resolve(result);
          }
          delete window.RNBridge.callbacks[callbackId];
        }
      });
      
      console.log('RNBridge 注入成功!');
      console.log('可用方法:', Object.keys(window.RNBridge).filter(key => key !== 'callbacks'));
      
      // 通知RN注入完成
      window.ReactNativeWebView.postMessage(JSON.stringify({
        type: 'bridgeReady',
        callbackId: 'ready'
      }));
    })();
  `;

  // WebView加载状态
  const renderLoading = () => (
    <View style={styles.loadingContainer}>
      <ActivityIndicator size="large" color="#007AFF" />
      <Text style={styles.loadingText}>加载中...</Text>
    </View>
  );

  // 处理WebView错误
  const handleError = syntheticEvent => {
    const { nativeEvent } = syntheticEvent;
    console.error('WebView错误:', nativeEvent);
    Alert.alert('加载失败', '页面加载失败,请检查网络后重试', [
      { text: '确定', onPress: () => navigation.goBack() },
    ]);
  };

  return (
    <View style={styles.container}>
      <WebView
        ref={webViewRef}
        source={{ uri: url }}
        onMessage={handleMessage}
        injectedJavaScript={injectedJavaScript}
        javaScriptEnabled={true}
        domStorageEnabled={true}
        startInLoadingState={true}
        renderLoading={renderLoading}
        onError={handleError}
        onHttpError={handleError}
        style={styles.webview}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFFFFF',
  },
  webview: {
    flex: 1,
  },
  loadingContainer: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 14,
    color: '#666',
  },
});

export default WebViewScreen;


h5页面代码

const onTestGetUserInfo = async () => {
 console.log('onTestGetUserInfo---start', window.RNBridge)
 try {
   const result = await window.RNBridge.getUserInfo({ id: 88 })
   console.log('getUserInfo---result', result)
 } catch (error) {
   console.error('getUserInfo---error', error)
 }
}
React中的路由

reactnative.cn/docs/next/n…

安装包
   "@react-navigation/bottom-tabs": "^7.15.5",
   "@react-navigation/native": "^7.1.33",
   "@react-navigation/native-stack": "^7.14.4",
   "react-native-screens": "^4.24.0",

React Navigation类比 React Router说明
NavigationContainerBrowserRouter根容器,提供导航上下文
Stack.NavigatorRoutes定义一组页面容器,有堆栈效果
Stack.ScreenRoute单个页面配置
Tab.Navigator无直接对应标签栏容器
Tab.Screen无直接对应标签页配置
useNavigationuseNavigate导航 Hook
useRouteuseParams获取路由参数
嵌套导航器的工作原理

NavigationContainer 下确实只能有一个根 Navigator,但这个根 Navigator 可以是 Stack.NavigatorTab.Navigator 等类型,而且它本身可以嵌套其他导航器。

deepseek_mermaid_20260310_9f096d.png

  • NavigationContainer 的作用:是一个顶层容器组件,它负责管理整个应用的导航状态,它的角色类似于 Web 开发中的 BrowserRouter

  • 根 Navigator 只能有一个,但可以嵌套:NavigationContainer 的直接子节点只能是一个 Navigator(无论是 Stack.Navigator 还是 Tab.Navigator 。这是 React Navigation 的规则,如果尝试放置两个平行的 Navigator,会抛出错误 。但这个唯一的根 Navigator 内部可以嵌套其他 Navigator。这正是 React Navigation 实现复杂路由的方式 

  • 嵌套导航器的核心机制:Stack.Screen 的 component 可以是另一个 Navigator 你观察到的现象——Stack.Screen 的 component 可以是 Tab.Navigator——这是嵌套导航器的核心用法 。 这种模式的含义是:将整个 Tab.Navigator 当作一个“页面”放进 Stack.Navigator 的一个 Screen 中。当导航到这个页面时,整个标签导航器就会被渲染出来。

路由示例
一个简单的路由示例
// 定义一个 Tab Navigator(这是一个独立的导航器组件)
function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={FeedScreen} />
      <Tab.Screen name="Messages" component={MessagesScreen} />
    </Tab.Navigator>
  );
}

// 根导航器
function RootStack() {
  return (
    <Stack.Navigator>
      {/* Stack.Screen 的 component 直接使用了 HomeTabs 这个导航器组件 */}
      <Stack.Screen
        name="Home"
        component={HomeTabs}
        options={{ headerShown: false }}
      />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </Stack.Navigator>
  );
}

// 顶层
export default function App() {
  return (
    <NavigationContainer>
      {/* 只有一个根 Navigator */}
      <RootStack />
    </NavigationContainer>
  );
}
一个具体的应用示例
  • index.js
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);

  • App.js
import { SafeAreaProvider } from 'react-native-safe-area-context';
import Router from './src/router';

const App = () => {
  return (
    <SafeAreaProvider>
      <Router />
    </SafeAreaProvider>
  );
};

export default App;
  • src/router.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

// 导入页面组件
import HomeScreen from '../screens/HomeScreen';
import OrderScreen from '../screens/OrderScreen';
import ProfileScreen from '../screens/ProfileScreen';
import WebViewScreen from '../screens/WebViewScreen';

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

// 路由配置(类似Vue Router的routes数组)
const routes = [
  {
    name: 'Home',
    component: HomeScreen,
    options: { title: '首页' },
    tabBar: true, // 标记为tab页面
  },
  {
    name: 'Orders',
    component: OrderScreen,
    options: { title: '订单' },
    tabBar: true,
  },
  {
    name: 'Profile',
    component: ProfileScreen,
    options: { title: '我的' },
    tabBar: true,
  },
  {
    name: 'WebView',
    component: WebViewScreen,
    options: { title: '加载中...' },
    tabBar: false, // 不是tab页面
  },
];

// 底部Tab导航(类似Vue的tabbar配置)
function TabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={{
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: 'gray',
      }}
    >
      {routes
        .filter(route => route.tabBar)
        .map(route => (
          <Tab.Screen
            key={route.name}
            name={route.name}
            component={route.component}
            options={route.options}
          />
        ))}
    </Tab.Navigator>
  );
}

// 根路由配置(类似Vue Router的根路由)
export default function Router() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Main"
          component={TabNavigator}
          options={{ headerShown: false }}
        />
        {routes
          .filter(route => !route.tabBar)
          .map(route => (
            <Stack.Screen
              key={route.name}
              name={route.name}
              component={route.component}
              options={route.options}
            />
          ))}
      </Stack.Navigator>
    </NavigationContainer>
  );
}
常用路由api
import { useNavigation, useRoute } from '@react-navigation/native';

  const navigation = useNavigation();
  const route = useRoute();
  
  • 导航
// 最基本的用法
navigation.navigate('Profile');

// 带参数跳转
navigation.navigate('Profile', { userId: '123' });

// 在详情页再次 push 到新的详情页(可以无限次)
//【`push` 和 `navigate` 的核心区别:`navigate` 如果已经在目标页面可能不会创建新实例,而 `push` **一定会**压入新页面。】
navigation.push('Detail', { id: '456' });

// 返回
navigation.goBack();

// 用新页面替换当前页面(用户无法返回当前页)
navigation.replace('Login');
  • 获取参数 route.params
import { useNavigation, useRoute } from '@react-navigation/native';

  const route = useRoute();
  const { userId } = route.params;
在 React Native 中发起数据请求
Fetch API
// GET 请求示例
const getMovies = async () => {
  try {
    const response = await fetch('https://reactnative.dev/movies.json');
    const json = await response.json();
    console.log(json.movies);
  } catch (error) {
    console.error('请求失败:', error);
  }
};

// POST 请求示例
const createPost = async (data) => {
  try {
    const response = await fetch('https://api.example.com/posts', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
    const result = await response.json();
    console.log('创建成功:', result);
  } catch (error) {
    console.error('请求失败:', error);
  }
};

优缺点

  • ✅ 零依赖,开箱即用 
  • ✅ 基于 Promise,支持 async/await
  • ❌ 功能相对基础,需要手动处理超时、拦截器等 
  • ❌ 在 Android 上存在一些已知的 Cookie 问题
Axios
状态共享方案 Zustand (类似 Pinia)
  • 安装
# 使用 npm 或者 yarn
npm install zustand
# 或者
yarn add zustand

"zustand": "^5.0.11"
  • 创建一个 Store 模块
// `src/stores/useCounterStore.js`

import { create } from 'zustand';

// 创建一个名为 useCounterStore 的 Hook
const useCounterStore = create((set, get) => ({
  // --- 1. 定义状态 (类似 Pinia 的 state) ---
  count: 0,
  step: 1,

  // --- 2. 定义动作 (类似 Pinia 的 actions) ---
  // 直接修改状态
  increment: () => set((state) => ({ count: state.count + state.step })),
  decrement: () => set((state) => ({ count: state.count - state.step })),
  // 通过传入值修改
  setStep: (newStep) => set({ step: newStep }),
  
  // 异步动作 (原生支持,无需中间件)
  incrementAsync: async () => {
    // 模拟一个 API 调用
    await new Promise(resolve => setTimeout(resolve, 1000));
    // 使用 get() 获取最新状态
    set({ count: get().count + get().step });
  },
}));

export default useCounterStore;
  • 在 React Native 组件中使用
// `src/screens/CounterScreen.js`

import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import useCounterStore from '../stores/useCounterStore';

const CounterScreen = () => {
  // --- 核心用法:通过选择器(selector)订阅特定的状态片段 ---
  // 只有这样,组件才会精确地在 count 变化时重新渲染
  const count = useCounterStore((state) => state.count);
  const step = useCounterStore((state) => state.step);
  
  // 获取 actions(actions 本身是稳定的,不会导致不必要的渲染)
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);
  const setStep = useCounterStore((state) => state.setStep);
  const incrementAsync = useCounterStore((state) => state.incrementAsync);

  return (
    <View style={styles.container}>
      <Text style={styles.text}>当前计数: {count}</Text>
      <Text style={styles.text}>当前步长: {step}</Text>
      <View style={styles.buttonRow}>
        <Button title="-" onPress={decrement} />
        <Button title="+" onPress={increment} />
      </View>
      <Button title="异步加" onPress={incrementAsync} />
      <View style={styles.buttonRow}>
        <Button title="设置步长为 2" onPress={() => setStep(2)} />
        <Button title="设置步长为 5" onPress={() => setStep(5)} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  text: { fontSize: 20, marginBottom: 10 },
  buttonRow: { flexDirection: 'row', marginVertical: 10 },
});

export default CounterScreen;

好用的UI库 react-native-paper

oss.callstack.com/react-nativ…

    "react-native-paper": "^5.15.0",
    "@react-native-vector-icons/material-design-icons": "^12.4.1",

推荐项目目录结构

my-rn-app/
├── src/
│   ├── assets/                # 静态资源 (图片、字体等)
│   │   ├── images/
│   │   └── fonts/
│   │
│   ├── components/             # **完全可复用的纯 UI 组件**
│   │   ├── Button/
│   │   │   ├── Button.js
│   │   │   └── Button.styles.js
│   │   ├── LoadingSpinner/
│   │   └── index.js            # 统一导出,方便引用
│   │
│   ├── config/                 # 配置文件 (环境变量、常量)
│   │   ├── constants.js        # API 地址、事件名等
│   │   └── env.js              # 环境判断
│   │
│   ├── hooks/                  # **共享的自定义 Hooks**
│   │   ├── useDebounce.js
│   │   ├── useAppState.js
│   │   └── index.js
│   │
│   ├── navigation/             # **导航器配置 (你提到的 router)**
│   │   ├── AppNavigator.js     # 根导航器 (Stack)
│   │   ├── HomeStack.js        # 首页内部导航
│   │   ├── BottomTabs.js       # 底部 Tab 配置
│   │   └── types.js            # 导航参数类型定义 (TS)
│   │
│   ├── screens/                 # **应用的主要页面 (按 Feature 划分)**
│   │   ├── Home/                # 首页模块
│   │   │   ├── HomeScreen.js    # 首页主页面
│   │   │   ├── components/      # **仅首页使用的组件**
│   │   │   │   └── BannerCarousel.js
│   │   │   └── useHomeData.js   # **仅首页使用的 Hook**
│   │   ├── Order/               # 订单模块
│   │   │   ├── OrderScreen.js
│   │   │   ├── OrderDetailScreen.js
│   │   │   ├── components/
│   │   │   │   └── OrderCard.js
│   │   │   └── useOrderList.js
│   │   ├── Profile/             # 我的模块
│   │   └── WebView/             # WebView 模块
│   │       ├── WebViewScreen.js
│   │       └── useWebViewBridge.js
│   │
│   ├── services/                # **API 请求层 (与后端交互)**
│   │   ├── apiClient.js         # axios 实例配置
│   │   ├── authApi.js           # 登录相关 API
│   │   ├── orderApi.js          # 订单相关 API
│   │   └── userApi.js
│   │
│   ├── stores/                  # **全局状态管理 (Zustand)**
│   │   ├── useUserStore.js      # 用户信息、Token
│   │   ├── useOrderStore.js     # 全局订单状态
│   │   ├── useSettingsStore.js  # 主题、语言等
│   │   └── index.js             # 统一导出 stores
│   │
│   ├── utils/                   # **纯工具函数**
│   │   ├── storage.js           # 封装 AsyncStorage/MMKV
│   │   ├── permission.js        # 权限请求工具
│   │   ├── format.js            # 日期、金额格式化
│   │   └── index.js
├── index.js                     # 入口文件
├── App.js                       # 根容器组件
├── android/                     # 原生 Android 代码
├── ios/                         # 原生 iOS 代码
├── .env                         # 环境变量
├── .eslintrc.js
├── .prettierrc
├── babel.config.js
├── metro.config.js
└── package.json