跨平台移动端 React Native

0 阅读6分钟

目前只有 ios 设备,以下只针对 iPhone 测试。

搭建 React Native 项目

1、前提:准备环境

  • 安装 node ,版本 v22+
  • 安装 nrm (NPM Registry Manager) ,负责管理 npm 的下载源,解决的是下载缓慢或失败的问题
  • 安装 nvm (Node Version Manager) ,负责管理 Node.js 的版本
# 安装 nrm
npm install -g nrm

image.png

2、安装依赖

  • 必须安装的依赖有:NodeWatchmanXcodeCocoaPods
  • 必须安装 Xcode 来获得编译 iOS 应用所需的工具和环境
  • CocoaPods 是用 Ruby 编写的包管理器(可以理解为针对 iOS 的 npm)。从 0.60 版本开始 react native 的 iOS 版本需要使用 CocoaPods 来管理依赖。
brew install watchman

watchman --version
# 2026.05.25.00

brew install cocoapods

3、命令创建 React native 项目

npx @react-native-community/cli init reactnative_app

启动

react-native start

作用单独启动 Metro Bundler 服务器(即打包服务)。

执行流程

  1. 启动一个本地的 HTTP 服务,默认监听 8081 端口。
  2. 监听项目文件变化,当 JS 或资源文件修改时,自动重新打包并通知客户端更新(热重载 / Fast Refresh)。
  3. 提供调试工具(如 Chrome DevTools)的连接端点。
npx react-native run-ios

作用:在 iOS 模拟器或真机上构建并运行 React Native 应用。

执行流程

  1. 启动 Metro Bundler(如果尚未启动):Metro 是 React Native 的 JavaScript 打包工具,负责将 JS 代码、资源等打包成 main.bundle 并提供热更新服务。
  2. 编译原生 iOS 项目:调用 Xcode 的命令行工具(xcodebuild)编译 iOS 项目的 .xcworkspace 或 .xcodeproj
  3. 安装并启动应用:将编译好的 .app 包安装到目标设备(模拟器或真机),并启动应用。
  4. 建立调试连接:连接 Metro 的调试端口,支持热重载和错误提示。

路由导航

实现路由导航

使用插件 @react-navigation/native-stack

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();
  <NavigationContainer>
    <Stack.Navigator initialRouteName="Home">
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="System" component={SystemScreen} />
      <Stack.Screen name="Profile" component={UserProfileScreen} />
      <Stack.Screen name="Record" component={Record} />
    </Stack.Navigator>
  </NavigationContainer>

点击按钮跳转

import { useNavigation } from '@react-navigation/native';
const navigation = useNavigation();
  <View>
    <Button
      onPress={() => {
        navigation.navigate('System');
      }}
    >
      Open System Settings
    </Button>
  </View> 

实现底部 Tab

使用插件 @react-navigation/bottom-tabs

function App() {
  const isDarkMode = useColorScheme() === 'dark';

  return (
    <SafeAreaProvider>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <NavigationContainer>
        <Tab.Navigator>
         
          <Tab.Screen name="Home" component={HomeScreen} />
          <Tab.Screen name="System" component={SystemScreen} />
          <Tab.Screen name="Profile" component={UserProfileScreen} />
          <Tab.Screen name="Record" component={Record} />
         
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

交互

Linking 打开其他 APP

使用 Linking.canOpenURL 检查百度 App 是否已安装,如果已安装,再用 Linking.openURL 将其打开

打开百度App

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>baiduboxapp</string>
</array>

URL Scheme 机制

URL Scheme 是一种让 App 之间能够相互调用的“通用语言”。它允许 App 注册自己独一无二的“网址”,其他 App 或网页浏览器通过这个“网址”就能直接唤醒它,并执行特定操作。

工作流程

  1. 注册:开发者在 App 里向系统声明自己要处理的 URL Scheme。
  2. 唤起:当系统遇到一个 URL(如 myapp://page),它会查找注册了该 Scheme 的 App。
  3. 处理:App 被唤醒后,系统将完整的 URL 传给 App,App 解析 URL 并跳转到对应页面或执行相应操作。

Linking 拨打电话

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>tel</string>
    <string>telprompt</string>
</array>
  <Button
    mode="outlined"
    onPress={() => {
      const phoneNumber = '13800000000';
      let url = `tel:${phoneNumber}`;
      // iOS 使用 telprompt 会先弹窗询问用户是否确认拨号,体验更友好
      if (Platform.OS === 'ios') {
        url = `telprompt:${phoneNumber}`;
      }

      // 3. 检查当前设备是否支持拨号功能
      Linking.canOpenURL(url)
        .then(supported => {
          if (!supported) {
            Alert.alert('提示', '您的设备不支持拨打电话功能');
          } else {
            // 4. 执行拨号
            return Linking.openURL(url);
          }
        })
        .catch(err => console.error('An error occurred', err));
    }}
  >
    拨打 电话
  </Button>

Vibration 控制设备振动/停止振动

  <Button
    mode="outlined"
    onPress={() => {
      const pattern = [100, 200, 100, 200];
      Vibration.vibrate(pattern, true); // 第二个参数为 true 表示循环
    }}
  >
    手机振动
  </Button>
  <View style={styles.placePlaceholder} />
  <Button
    mode="outlined"
    onPress={() => {
      Vibration.cancel();
    }}
  >
    手机停止 振动
  </Button>

获取当前位置

yarn add @react-native-community/geolocation
import Geolocation, {
  type GeolocationResponse,
} from '@react-native-community/geolocation';
  <Button
    mode="outlined"
    onPress={() => {
      Geolocation.getCurrentPosition(
        (p: GeolocationResponse) => {
          console.log('当前位置:', p);
          setPosition(p);
          // position.coords.latitude, position.coords.longitude
        },
        error => {
          console.log('获取位置失败:', error.code, error.message);
        },
        {
          enableHighAccuracy: true, // 启用高精度 (使用GPS)
          timeout: 15000, // 超时时间 (毫秒)
          maximumAge: 10000, // 位置缓存有效期 (毫秒)
        },
      );
    }}
  >
    获取当前位置
  </Button>

image.png

检测蓝牙状态,引导设置

yarn add react-native-ble-plx
<key>NSBluetoothAlwaysUsageDescription</key>
<string>你的应用需要使用蓝牙的原因</string>

打开相机/图库

yarn add react-native-image-picker
<string>我们需要访问您的相机来拍照</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>我们需要访问您的相机来拍照</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>此应用需要访问您的相册来选择照片</string>

使用字体图标

react-native-vector-icons 需要手动添加字体到 iOS 工程,否则图标字体无法加载,只显示空框/不显示。

<key>UIAppFonts</key>
<array>
    <string>MaterialCommunityIcons.ttf</string>
</array>

在 底部导航栏 配置 icon

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

const Tab = createBottomTabNavigator();

function HomeIcon({ color, size }: { color: string; size: number }) {
  return <Icon source="home" color={color} size={size} />;
}

function AccountIcon({ color, size }: { color: string; size: number }) {
  return <Icon source="account" color={color} size={size} />;
}

function App() {
  const isDarkMode = useColorScheme() === 'dark';

  return (
    <SafeAreaProvider>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <NavigationContainer>
        <Tab.Navigator>
         
          <Tab.Screen
            name="Home"
            component={HomeScreen}
            options={{
              tabBarIcon: HomeIcon,
            }}
          />
          <Tab.Screen name="System" component={SystemScreen} />
          <Tab.Screen name="Profile" component={UserProfileScreen}
            options={{
              tabBarIcon: AccountIcon,
            }}
          />
          <Tab.Screen name="Record" component={Record} />
        
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

Bridge(桥接)原理

传统 Bridge

JS 和 Native 是独立环境,通过异步消息队列通信,数据需经过 JSON 序列化/反序列化,存在性能瓶颈。

JSI (JavaScript Interface)

RN 0.60+ 推出的接口,让 JS 可以直接持有 Native 对象的引用,实现同步调用,无需序列化

Hermes 引擎是什么?

Hermes 是 Facebook 专为 RN 打造的轻量级 JS 引擎。

主要优势:

  1. 启动更快:支持将 JS 源码预编译为字节码,省去运行时解析 AST 的过程。
  2. 内存占用更低:比 JS Core 节省约 50% 的内存。
  3. 包体积更小:字节码格式更紧凑

JSI 架构

JSI 的核心是为 C++ 应用嵌入 JavaScript 引擎提供了一套标准 API。这套 API 让 C++ 代码可以直接操作 JavaScript 的对象和函数,反之亦然。

  1. 直接引用:通过 JSI,JavaScript 可以持有对 C++ 对象的引用(HostObject),并像调用普通 JS 对象一样调用它。
  2. 同步调用:由于是直接引用,JS 调用原生方法可以立即得到返回值,无需通过异步队列等待。
  3. 数据零拷贝:数据在 JS 和原生层之间传递时,不再需要序列化为 JSON 字符串,而是直接传递 C++ 对象或基础类型,大大减少了内存和 CPU 开销。

navite module

在 RN 中,Native Module主要分为旧版和新版(Turbo Module)两种:

旧版 Native Modules

基于Bridge通信,在启动时会一次性加载所有模块。适合快速原型或维护老项目

新版 实现一个 Turbo Module 的通用步骤

  1. 编写类型规范 (Spec) :在specs目录下创建NativeXXX.ts文件,用TypeScript或Flow定义模块接口,文件名必须以Native为前缀。
  2. 配置 Codegen:在package.json中添加codegenConfig字段,告诉工具去哪里找规范文件。
  3. 实现原生代码
    • Android:用Java/Kotlin创建类,继承ReactContextBaseJavaModule并实现方法。
    • iOS:用Objective-C/Swift创建类,实现RCTBridgeModule协议。
    • 跨平台C++ :在shared文件夹中创建.h.cpp文件,实现核心逻辑,实现一次代码在双端共享。
  4. 注册模块:将实现注册到ReactPackage中。
  5. 在JS中调用:直接导入并使用生成的模块对象。

如何优化 React Native 应用的性能?

  • 列表优化:长列表务必使用 FlashList 替代 FlatList,前者性能远优于后者。
  • 渲染优化:使用 React.memouseCallback 和 useMemo 避免不必要的组件重渲染。
  • 动画优化:使用 React Native Reanimated,它的动画在 UI 线程执行,不会因为 JS 线程繁忙而卡顿。
  • 引擎与架构:开启 Hermes 引擎,并确保项目兼容新架构

FlatListScrollView 的区别是什么?如何优化长列表?

  • 核心区别:ScrollView 会一次性渲染所有子组件,适合内容很少的视图;FlatList 采用虚拟化(Virtualization) 技术,只渲染当前屏幕可见的元素,适合长列表。
  • 优化手段:为 FlatList 设置 getItemLayout 属性来跳过测量过程;在 keyExtractor 中提供稳定的 key;避免在 renderItem 中使用匿名函数。

最后

  1. 文档