ReactNative 快速入门手册

1. 项目初始化

1.1. 创建新应用

如果之前安装了全局的 react-native-cli 包,请先将其卸载,以避免潜在的问题:

javascript 复制代码
npm uninstall -g react-native-cli @react-native-community/cli

接下来,使用 React Native Community CLI 来生成一个新的项目。我们将创建一个名为 "MyApp" 的新 React Native 项目:

javascript 复制代码
npx @react-native-community/cli@latest init MyApp

如果你是在现有应用中集成 React Native,或者在项目中安装了 Expo,或者为现有 React Native 项目添加 Android 支持,则不需要此步骤。你也可以使用第三方 CLI来设置 React Native 应用。

在初始化安装阶段,一定不要中断。

如果你在 iOS 上遇到问题,请尝试通过以下步骤重新安装依赖项:

javascript 复制代码
cd ios  # 进入 ios 文件夹
bundle install  # 安装 Bundler
bundle exec pod install  # 安装 CocoaPods 管理的 iOS 依赖

如果你想使用特定的 React Native 版本创建新项目,可以使用 --version 参数:

javascript 复制代码
npx @react-native-community/cli@X.XX.X init MyApp --version X.XX.X

你也可以使用 --template 参数开始使用自定义的 React Native 模板,了解更多信息可以查看文档。

1.2. 启动 Metro

Metro 是 React Native 的 JavaScript 构建工具。要启动 Metro 开发服务器,请在项目文件夹中运行以下命令:

javascript 复制代码
yarn start

**注意:**如果你熟悉 Web 开发,Metro 类似于 Vite 和 webpack 等打包工具,但专为 React Native 设计。例如,Metro 使用 Babel 将 JSX 等语法转换为可执行的 JavaScript。

1.3. 启动应用程序

让 Metro Bundler 在其自己的终端中运行。然后在你的 React Native 项目文件夹中打开一个新终端,运行以下命令

javascript 复制代码
yarn android

如果一切设置正确,应该很快看到新应用在 Android 模拟器中运行。

这是一种程序的启动方法,你也可以直接通过 Android Studio 来运行。

1.4. 工程化说明

  1. 项目结构

创建项目后,我们优化目录结构,如下:

javascript 复制代码
MyApp/
├── android/           # Android 原生代码
├── ios/               # iOS 原生代码
├── node_modules/      # npm 依赖包
├── src/               # 应用源代码
│   ├── components/    # 可复用组件
│   ├── screens/       # 页面组件
│   ├── navigation/    # 路由导航
│   ├── assets/        # 静态资源(图片、字体等)
│   ├── services/      # 网络请求或其他服务
│   └── utils/        # 工具函数
├── App.js            # 应用主入口
├── package.json      # npm 配置文件
├── babel.config.js   # Babel 配置
├── metro.config.js   # Metro bundler 配置
└── index.js          # React Native 入口文件
  1. 重要文件与目录
  • android/: 包含 Android 原生代码和配置。你可以在这里修改 Android 特性、添加依赖或处理本地代码。

  • ios/: 包含 iOS 原生代码和配置。可以在这里修改 iOS 特性、添加依赖或处理本地代码。

  • src/: 应用的主要源代码目录。通常推荐将所有应用逻辑和组件放在这里,以便于管理和组织。

    • components/: 存放可复用的 UI 组件,通常是小的、独立的组件。

    • screens/: 应用的各个页面,通常是较大的组件,代表不同的视图或功能模块。

    • navigation/: 处理应用的路由和导航逻辑,通常会使用 @react-navigation/native 等库来管理导航。

  • assets/: 存放图片、字体和其他静态资源。

  • services/: 处理网络请求、API 调用或其他服务逻辑。

  • utils/: 存放通用的工具函数,例如格式化日期、处理字符串等。

  • App.js: 应用的主组件,是应用的入口点,通常在这里设置全局状态或根组件。

  • package.json: 定义项目的依赖和脚本。可以在这里添加或更新项目的 npm 包。

  • babel.config.js: Babel 的配置文件,用于转译 JavaScript 代码。

  • metro.config.js: Metro bundler 的配置文件,可以在这里自定义 bundler 行为,例如资源解析。

  • index.js: React Native 应用的入口文件,通常用于注册主组件并启动应用。

  1. 项目配置
  • 依赖管理: 使用 npm 或 yarn 管理项目依赖,可以在 package.json 中查看和添加依赖。

  • 环境配置: 可以根据不同的环境需求在 ios 和 android 文件夹内配置项目设置,例如 API 地址、调试工具等。

  • 样式: 可以使用 StyleSheet 来管理样式,或引入 styled-components 等库进行更灵活的样式管理。

1.5. 关于 Expo 框架

使用 Expo 确实可以简化开发,但是又会多一些学习成本,所以对于初学者来说,我建议不要使用任何框架,而是直接用原生 React-Native 来完成开发,这样对于 React-Native 的学习才能更深入。

Expo 链接:https://github.com/expo

2. 常用组件与 API

2.1. 常用组件

React Native 提供了多种内置组件,方便开发者快速构建用户界面。以下是一些常用组件及其用法。

  1. View

用法:View 是 React Native 中最基本的组件,用于构建 UI 的布局。它可以容纳其他组件,并支持样式和事件处理。这个组件可以当做 div 用。

javascript 复制代码
import { View } from 'react-native';

const MyComponent = () => {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      {/* 其他组件 */}
    </View>
  );
};
  1. Text

用法:Text 组件用于显示文本。支持多种文本样式,如字体、颜色、大小等。

javascript 复制代码
import { Text } from 'react-native';

const MyText = () => {
  return (
    <Text style={{ fontSize: 20, color: 'blue' }}>
      Hello, React Native!
    </Text>
  );
};
  1. Image

用法:Image 组件用于显示图片,支持本地图片和网络图片。

javascript 复制代码
import { Image } from 'react-native';

const MyImage = () => {
  return (
    <Image
      source={{ uri: 'https://example.com/image.png' }}
      style={{ width: 100, height: 100 }}
    />
  );
};
  1. ScrollView

用法:ScrollView 组件用于创建可滚动的视图,适用于内容较多的场景。

javascript 复制代码
import { ScrollView, Text } from 'react-native';

const MyScrollView = () => {
  return (
    <ScrollView>
      <Text>Item 1</Text>
      <Text>Item 2</Text>
      <Text>Item 3</Text>
      {/* 更多内容 */}
    </ScrollView>
  );
};
  1. TextInput

用法:TextInput 组件用于接收用户输入。可以设置占位符、样式等属性。

javascript 复制代码
import { TextInput } from 'react-native';

const MyTextInput = () => {
  return (
    <TextInput
      style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
      placeholder="Type here"
    />
  );
};
  1. Button

用法:Button 组件用于创建按钮,支持点击事件。

javascript 复制代码
import { Button } from 'react-native';

const MyButton = () => {
  return (
    <Button
      title="Click Me"
      onPress={() => alert('Button pressed!')}
    />
  );
};
  1. FlatList

用法:FlatList 组件用于高效地渲染长列表,支持性能优化。

javascript 复制代码
import { FlatList, Text } from 'react-native';

const DATA = [
  { id: '1', title: 'Item 1' },
  { id: '2', title: 'Item 2' },
  { id: '3', title: 'Item 3' },
];

const MyFlatList = () => {
  return (
    <FlatList
      data={DATA}
      renderItem={({ item }) => <Text>{item.title}</Text>}
      keyExtractor={item => item.id}
    />
  );
};
  1. TouchableOpacity

用法:TouchableOpacity 组件用于实现可点击的元素,支持透明度变化。

javascript 复制代码
import { TouchableOpacity, Text } from 'react-native';

const MyTouchable = () => {
  return (
    <TouchableOpacity onPress={() => alert('Tapped!')}>
      <Text style={{ fontSize: 20 }}>Tap Me</Text>
    </TouchableOpacity>
  );
};
  1. Modal

用法:Modal 组件用于创建模态窗口,适用于需要用户注意的对话框。

javascript 复制代码
import { Modal, View, Text, Button } from 'react-native';

const MyModal = () => {
  const [modalVisible, setModalVisible] = useState(false);

  return (
    <View>
      <Button title="Show Modal" onPress={() => setModalVisible(true)} />
      <Modal
        transparent={true}
        visible={modalVisible}
        animationType="slide"
      >
        <View style={{ marginTop: 50 }}>
          <Text>This is a Modal!</Text>
          <Button title="Hide Modal" onPress={() => setModalVisible(false)} />
        </View>
      </Modal>
    </View>
  );
};
  1. SafeAreaView

用法:用于给页面给出安全区域的处理

javascript 复制代码
<SafeAreaView style={backgroundStyle}>
  <StatusBar />
</SafeAreaView>

2.2. 样式表

在 React Native 中,样式表的管理和应用非常重要。

React Native 提供了 StyleSheet API,方便开发者创建和管理组件的样式。以下是对 StyleSheet API 的详细介绍,包括主题的实现方案和 StyleSheet 的原理。

  1. Stylesheet API

(1). 基本用法

StyleSheet API 允许开发者创建样式对象,使用 CSS 样式的语法来定义样式属性。这些样式对象可以直接应用到组件上,提高了样式的可读性和可维护性。

javascript 复制代码
import { StyleSheet, View, Text } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5fcff',
  },
  text: {
    fontSize: 20,
    color: 'blue',
  },
});

const MyComponent = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello, React Native!</Text>
    </View>
  );
};

(2). 性能优化

使用 StyleSheet.create 方法创建样式对象的一个主要好处是性能优化。React Native 会在内部对样式进行优化,避免在每次渲染时重新计算样式,从而提高性能。

  1. 主题的实现方案

主题在应用中是一个重要的概念,允许开发者根据不同的环境(如浅色模式和深色模式)或用户偏好动态切换样式。以下是一些实现方案:

(1). 使用 Context API

可以使用 React 的 Context API 来创建主题上下文,允许在应用中轻松访问当前主题。

javascript 复制代码
import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light'); // 默认主题

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const useTheme = () => useContext(ThemeContext);

(2). 在组件中使用主题

通过 useTheme Hook 可以在组件中访问和使用当前主题。

javascript 复制代码
const ThemedComponent = () => {
  const { theme } = useTheme();
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: theme === 'light' ? '#fff' : '#333',
    },
    text: {
      color: theme === 'light' ? '#000' : '#fff',
    },
  });

  return (
    <View style={styles.container}>
      <Text style={styles.text}>Current Theme: {theme}</Text>
    </View>
  );
};
  1. Stylesheet 的原理

React Native 的 StyleSheet API 处理样式的过程包括多个步骤,从样式的创建到应用于组件,涉及到性能优化、样式合并和跨平台支持等多个方面。以下是对这一过程的深入分析。

(1). 样式的创建

当你调用 StyleSheet.create 时,React Native 会进行以下操作:

  • 样式对象的定义:你定义的样式对象被存储为一个不可变的对象。这意味着在样式创建后,它的属性不会被修改,从而提高了性能,因为 React Native 不需要在每次渲染时重新计算样式。

  • 映射到原生代码:React Native 将这些样式对象映射到原生代码中。这是通过将 JavaScript 对象转换为原生视图的样式属性来实现的。这种转换使得 React Native 能够与 Android 和 iOS 的原生 UI 组件进行交互。

(2). 样式的存储与优化

在调用 StyleSheet.create 后,样式会被存储在内存中。React Native 维护一个样式表缓存,以避免在每次渲染时重新创建样式。具体过程如下:

  • 缓存机制:样式表在应用中被缓存,React Native 会通过样式的 ID 来引用它们。这种机制确保了样式的快速访问。

  • 样式的优化:在创建样式时,React Native 会分析每个样式的属性,决定哪些属性可以在原生端高效处理,哪些属性需要进行额外的处理。例如,flex、padding、margin 等布局相关属性会被优化为原生布局引擎可以直接使用的形式。

(3). 样式的应用

将样式应用到组件的过程相对复杂,涉及以下步骤:

  • 组件渲染:当一个组件被渲染时,React Native 会检查其属性中的 style 属性。

  • 样式合并:如果组件的 style 属性是一个数组,React Native 会将所有样式合并。合并过程中后面出现的样式会覆盖前面样式的同名属性。可以通过特定的方式将多个样式合并成一个对象,以便进行比较或传递。

javascript 复制代码
const styles = [baseStyle, additionalStyle];
const combinedStyle = StyleSheet.flatten(styles);
  • 应用样式到原生组件:合并后的样式通过桥接(Bridge)机制发送到原生层。React Native 使用 JSON 格式将这些样式传递给原生视图。

(4). 样式的更新与重新渲染

  • 响应式更新:当组件的状态或属性变化时,React 会触发重新渲染。此时,它会重新计算需要应用的样式并根据新的状态合并样式。

  • 原生与 JavaScript 的交互:React Native 通过异步的方式与原生代码进行交互,确保在样式更新时不会阻塞 UI 渲染。样式的变化通过消息传递机制推送到原生层,原生组件会在下一个渲染周期中更新样式。

(5). 跨平台支持

React Native 还提供了平台特定的样式支持。通过 Platform 模块,开发者可以为不同的平台定义特定的样式,这些样式在创建时被单独处理,以确保在 Android 和 iOS 上有最佳的表现。

javascript 复制代码
import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  button: {
    backgroundColor: Platform.OS === 'ios' ? 'blue' : 'green',
    padding: 10,
  },
});

2.3. 路由处理

在 React Native 中,路由处理是应用导航的重要组成部分。

通过使用 @react-navigation 库,可以轻松地创建和管理不同的屏幕和导航结构。以下是对你提供的代码示例的详细讲解。

  1. 项目依赖

在 package.json 中,你列出了几个与路由相关的依赖:

javascript 复制代码
"@react-navigation/bottom-tabs": "6.6.1",
"@react-navigation/native": "6.1.18",
"@react-navigation/stack": "6.4.1",
"@react-navigation/native-stack": "6.11.0",
"react-native-screens": "3.35.0",
"react-native-safe-area-context": "4.14.0"
  • @react-navigation/native: 核心导航库,提供基本的导航功能。

  • @react-navigation/bottom-tabs: 用于创建底部标签导航的库。

  • @react-navigation/native-stack: 提供原生堆栈导航的实现,具有更好的性能。

  • react-native-screens: 优化应用性能的库,支持原生屏幕管理。

  • react-native-safe-area-context: 提供安全区域支持,确保在刘海屏或其他特殊屏幕形状下的内容显示正确。

  1. 创建底部标签导航

在 src/routers/index.tsx 中,你首先创建了底部标签导航组件:

javascript 复制代码
const TabBar = createBottomTabNavigator();

function BottomBar() {
  return (
    <TabBar.Navigator
      screenOptions={{
        headerShown: false,
      }}>
      <TabBar.Screen
        name="TabHome"
        component={HomeScreen}
        options={{
          tabBarLabel: '首页',
        }}
      />
      <TabBar.Screen
        name="TabOther"
        component={DetailScreen}
        options={{
          tabBarLabel: '其他',
        }}
      />
    </TabBar.Navigator>
  );
}
  • createBottomTabNavigator: 创建底部标签导航器。

  • TabBar.Navigator: 定义底部导航的容器。

  • TabBar.Screen: 定义每个标签页及其对应的组件。

headerShown: false 表示在底部标签导航中不显示头部。

  1. 创建堆栈导航

接下来,创建一个堆栈导航组件,用于管理更复杂的导航结构:

javascript 复制代码
const Stack = createNativeStackNavigator();

function Navigator() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        screenOptions={{
          headerTitleAlign: 'center',
          headerBackTitleVisible: false,
          gestureEnabled: true,
          gestureDirection: 'horizontal',
          ...Platform.select({
            android: {
              headerStatusBarHeight: StatusBar.currentHeight,
            },
          }),
          headerStyle: {
            backgroundColor: Colors.primary,
          },
        }}>
        <Stack.Screen name="BottomBar" component={BottomBar} />
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailScreen} />
        <Stack.Screen name="Topics" component={TopicsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
  • createNativeStackNavigator: 创建一个原生堆栈导航器。

  • NavigationContainer: 作为导航树的根容器,提供路由上下文。

  • Stack.Navigator: 定义堆栈导航的容器。

  • Stack.Screen: 定义堆栈中的每个屏幕及其对应的组件。

⚙️ 执行 pod 安装依赖:npx pod-install ios,如果不成功可能是网络问题,或者 进到 ios 文件夹,执行 pod install

如果是安装了原生内容,需要 pod 构建,我建议大家用 Xcode 打开。

  1. 路由处理过程
  • 导航结构:在此示例中,定义了一个底部标签导航(BottomBar)和多个堆栈屏幕(Home、Details、Topics)。用户可以在底部标签之间切换,同时也可以通过堆栈导航访问详细信息和主题。

  • 屏幕的渲染:当用户切换到某个标签时,BottomBar 中的对应组件会被渲染。在这个底部导航中,HomeScreen 和 DetailScreen 是两个标签页。

  • 堆栈导航:如果在 HomeScreen 中,我们希望导航到 DetailScreen,可以使用 navigation.navigate('Details') 方法来实现。这将把 DetailScreen 推入堆栈中,并自动管理返回操作。

  • 状态管理:在导航中,React Navigation 会自动管理导航状态,例如当前活动的屏幕、参数传递等。每当导航状态变化时,相关的组件会重新渲染以反映新的状态。

  1. 主题和样式

在 Navigator 函数中,你还通过 screenOptions 为导航器设置了一些样式和行为选项:

  • headerTitleAlign: 设置头部标题的对齐方式。

  • headerBackTitleVisible: 是否在返回按钮上显示标题。

  • gestureEnabled: 是否启用手势返回功能。

  • headerStyle: 设置头部的样式,例如背景颜色。

2.4. 常用API

  1. AsyncStorage

用于本地存储简单数据,以键值对的形式存储。

javascript 复制代码
import AsyncStorage from '@react-native-async-storage/async-storage';

/// 存储数据
const storeData = async (value) => {
  try {
    await AsyncStorage.setItem('@storage_Key', value);
  } catch (e) {
    // 保存错误
  }
};

// 读取数据
const getData = async () => {
  try {
    const value = await AsyncStorage.getItem('@storage_Key');
    if (value !== null) {
      // 值已存在
    }
  } catch (e) {
    // 读取错误
  }
};
  1. Fetch API

用于进行网络请求,类似于浏览器的 fetch。

javascript 复制代码
const fetchData = async () => {
  try {
    let response = await fetch('https://api.example.com/data');
    let json = await response.json();
    console.log(json);
  } catch (error) {
    console.error(error);
  }
};
  1. Linking

用于处理外部链接,如打开网页或拨打电话。

javascript 复制代码
import { Linking } from 'react-native';

// 打开网页
const openURL = async (url) => {
  const supported = await Linking.canOpenURL(url);
  if (supported) {
    await Linking.openURL(url);
  }
};

// 拨打电话
const makeCall = (phoneNumber) => {
  Linking.openURL(`tel:${phoneNumber}`);
};
  1. DeviceInfo

获取设备信息,如型号、系统版本等,需安装 react-native-device-info

javascript 复制代码
import DeviceInfo from 'react-native-device-info';

const getDeviceInfo = async () => {
  const deviceId = await DeviceInfo.getDeviceId();
  const systemVersion = await DeviceInfo.getSystemVersion();
  console.log(`Device ID: ${deviceId}, System Version: ${systemVersion}`);
};
  1. Permissions

请求和检查设备权限,需安装 react-native-permissions。

javascript 复制代码
import { PermissionsAndroid } from 'react-native';

const requestLocationPermission = async () => {
  try {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
      {
        title: '位置权限',
        message: '需要访问您的位置',
        buttonNeutral: '稍后询问',
        buttonNegative: '取消',
        buttonPositive: '确认',
      }
    );
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      console.log('您可以使用位置');
    } else {
      console.log('位置权限被拒绝');
    }
  } catch (err) {
    console.warn(err);
  }
};
  1. Vibration

控制设备震动。

javascript 复制代码
import { Vibration } from 'react-native';

// 震动1000毫秒
const triggerVibration = () => {
  Vibration.vibrate(1000);
};
  1. Share

用于分享内容到其他应用,如社交媒体。

javascript 复制代码
import { Share } from 'react-native';

const shareMessage = async () => {
  try {
    const result = await Share.share({
      message: '这是要分享的消息',
    });
    if (result.action === Share.sharedAction) {
      if (result.activityType) {
        // 共享到特定活动
      } else {
        // 仅共享
      }
    } else if (result.action === Share.dismissedAction) {
      // 共享被取消
    }
  } catch (error) {
    alert(error.message);
  }
};
  1. StatusBar

控制状态栏的样式和行为。

javascript 复制代码
import { StatusBar } from 'react-native';

// 设置状态栏样式
const setStatusBar = () => {
  StatusBar.setBarStyle('light-content', true);
  StatusBar.setBackgroundColor('#000000');
};

3. 多端开发方案

在开发跨平台移动应用时,React Native 提供了一系列工具和策略,以便在 iOS 和 Android 之间实现良好的适配。这一适配思路不仅确保了应用的功能一致性,还能针对不同平台的特性优化用户体验。以下是具体的适配思路和方法:

3.1. 使用 Platform API

React Native 中的 Platform 模块是实现跨平台适配的基础。它提供了对当前平台的识别能力,使开发者能够根据不同平台调整组件的渲染和样式。

常用属性与方法:

  • Platform.OS:返回当前运行的操作系统,可以用于条件渲染。

  • Platform.select():根据当前平台返回适合的值,允许为每个平台定义不同的样式或组件。

javascript 复制代码
import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      android: { backgroundColor: 'green' },
      ios: { backgroundColor: 'red' },
      default: { backgroundColor: 'blue' },
    }),
  },
});

这种方式使得代码更具可读性和可维护性,同时减少了条件语句的复杂性。

3.2. 利用 React Native 提供的常量

React Native 还提供了一些平台特定的常量,可以帮助开发者获取设备信息,从而优化适配。例如,使用 PlatformConstants(在最新版本中可能直接通过Platform或其他模块体现,此处为原文本内容)来获取设备的硬件和系统信息,这些信息可用于条件渲染或功能限制。

常用常量:

  • isPad:判断当前设备是否为 iPad。

  • forceTouchAvailable:判断当前设备是否支持 3D Touch(仅限 iOS)。

  • osVersion:获取当前操作系统的版本信息。

javascript 复制代码
import { Platform } from 'react-native';

if (Platform.isPad) {
  // 针对 iPad 的特定逻辑
}

3.3. 适配图片和资源

在处理多端时,图片资源的管理也非常重要。可以根据不同平台选择不同的图片资源,以确保最佳的加载和显示效果。

javascript 复制代码
const imageSource = Platform.select({
  ios: require('./images/image_ios.png'),
  android: require('./images/image_android.png'),
});

3.4. 使用第三方库

许多第三方库提供了跨平台支持,使得开发者能够更轻松地实现平台适配。例如,使用 react-native-elements 或 react-native-paper 可以获取一致的 UI 组件,这些组件自动适配不同的平台。

3.5. 性能和体验优化

除了视觉适配外,性能和用户体验也是跨平台开发中不可忽视的部分。

可以根据不同平台的特性,调整动画效果、事件响应等。例如,Android 上的返回按钮行为可能与 iOS 的导航模式有所不同,这需要在逻辑上进行适配。

4. 原生模块开发与接入

你的应用可能需要一些平台功能,但这些功能并不是直接通过 React Native 或社区维护的众多第三方库提供的。你可能想要复用一些现有的 Objective-C、Swift、Java、Kotlin 或 C++ 代码。React Native 提供了一套强大的 API,帮助你将原生代码与 JavaScript 应用代码连接起来。

原生内容分为两部分:

  • 原生模块:没有用户界面的原生库,比如持久存储、通知、网络事件等。这些功能可以作为 JavaScript 函数和对象供用户使用。

  • 原生组件:可以通过 React 组件在你的应用的 JavaScript 代码中使用的原生视图、小部件和控制器。

在过往的开发中,我们统一使用 NativeModules,但是新的架构下,React-Native 抽象出了两个新概念用于使用与新架构下的原生模块与原生组件,分别为:

  • Turbo Native Module

  • Fabric Native Components

NativeModules 是已弃用的原生模块和组件 API。尽管如此,由于我们的互操作层,你仍然可以在新架构下使用很多旧版库。你可以考虑:

  • 使用替代库;

  • 升级到对新架构支持更好的新版本库;

  • 将这些库自己移植为 Turbo 原生模块或 Fabric 原生组件。

原生组件的开发与接入请点击此处查看参考文档,本文中介绍原生模块的开发与接入。

4.1. 原生模块开发与接入 (iOS)

我们来实现 Web Storage API 的 localStorage。

为了让它在移动端工作,我们需要使用 Android 和 iOS 的 API:

  • Android: SharedPreferences

  • iOS: NSUserDefaults

  1. 声明类型规范

React Native 提供了一个名为 Codegen 的工具,可以根据用 TypeScript 或 Flow 编写的规范生成适用于 Android 和 iOS 的平台特定代码。这个规范声明了在你的原生代码与 React Native JavaScript 运行时之间传递的方法和数据类型。Turbo 原生模块既是你的规范,也是你编写的原生代码,以及从规范生成的 Codegen 接口。

创建一个规范文件:在应用根目录下创建一个名为 specs 的新文件夹,文件夹里创建一个名为 NativeLocalStorage.ts 的新文件。

以下是 localStorage 规范的实现:

javascript 复制代码
// specs/NativeLocalStorage.ts
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
  setItem(value: string, key: string): void;
  getItem(key: string): string | null;
  removeItem(key: string): void;
  clear(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocalStorage') as Spec;
  1. 配置 Codegen 运行

React Native Codegen 工具会根据规范生成平台特定的接口和样板代码。为此,Codegen 需要知道在哪里找到我们的规范以及该如何处理它。更新你的 package.json,如下:

javascript 复制代码
{
  "scripts": {
    "start": "react-native start",
    "test": "jest"
  },
  "codegenConfig": {
    "name": "NativeLocalStorageSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.nativelocalstorage"
    }
  },
  "dependencies": {
   ...
  }
}

在将所有内容连接到 Codegen 后,我们需要准备我们的原生代码。

CocoaPods 生成的项目中,通过脚本自动运行 Codegen。

javascript 复制代码
cd ios
bundle install
bundle exec pod install

输出内容大致如下:

javascript 复制代码
...
Framework build type is static library
[Codegen] Adding script_phases to ReactCodegen.
[Codegen] Generating ./build/generated/ios/ReactCodegen.podspec.json
[Codegen] Analyzing /Users/me/src/TurboModuleExample/package.json
[Codegen] Searching for codegen-enabled libraries in the app.
[Codegen] Found TurboModuleExample
[Codegen] Searching for codegen-enabled libraries in the project dependencies.
[Codegen] Found react-native
...
  1. 使用 Turbo 原生模块编写应用代码

使用 NativeLocalStorage,以下是修改后的 App.tsx,它包含了一些我们希望持久化的文本、一个输入字段和一些按钮来更新该值。

TurboModuleRegistry 支持两种获取 Turbo 原生模块的方式:

  • get<T>(name: string): T | null:如果 Turbo 原生模块不可用,则返回 null。

  • getEnforcing<T>(name: string): T:如果 Turbo 原生模块不可用,则抛出异常。假定该模块始终可用。

javascript 复制代码
// App.tsx
import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  Text,
  TextInput,
  Button,
} from 'react-native';

import NativeLocalStorage from './specs/NativeLocalStorage';

const EMPTY = '<empty>';

function App(): React.JSX.Element {
  const [value, setValue] = React.useState<string | null>(null);
  const [editingValue, setEditingValue] = React.useState<string | null>(null);

  React.useEffect(() => {
    const storedValue = NativeLocalStorage?.getItem('myKey');
    setValue(storedValue ?? '');
  }, []);

  function saveValue() {
    NativeLocalStorage?.setItem(editingValue ?? EMPTY, 'myKey');
    setValue(editingValue);
  }

  function clearAll() {
    NativeLocalStorage?.clear();
    setValue('');
  }

  function deleteValue() {
    NativeLocalStorage?.removeItem(editingValue ?? EMPTY);
    setValue('');
  }

  return (
    <SafeAreaView style={{flex: 1}}>
      <Text style={styles.text}>
        当前存储的值是:{value ?? '无值'}
      </Text>
      <TextInput
        placeholder="请输入要存储的文本"
        style={styles.textInput}
        onChangeText={setEditingValue}
      />
      <Button title="保存" onPress={saveValue} />
      <Button title="删除" onPress={deleteValue} />
      <Button title="清空" onPress={clearAll} />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  text: {
    margin: 10,
    fontSize: 20,
  },
  textInput: {
    margin: 10,
    height: 40,
    borderColor: 'black',
    borderWidth: 1,
    paddingLeft: 5,
    paddingRight: 5,
    borderRadius: 5,
  },
});

export default App;
  1. 编写原生平台代码

准备好后,我们将开始编写原生平台代码。这个过程分为两个部分:

注意:本指南展示的是如何创建一个仅适用于新架构的 Turbo 原生模块。如果你需要同时支持新架构和旧架构,请参阅我们的向后兼容性指南。

现在我们开始编写 iOS 平台代码,确保 localStorage 在应用关闭后仍然可用。

第一步:准备Xcode项目。

我们需要使用 Xcode 准备 iOS 项目。完成以下六个步骤后,你将拥有实现生成的 NativeLocalStorageSpec 接口的 RCTNativeLocalStorage。

(1). 打开 CocoaPods 生成的 Xcode 工作区。

bash 复制代码
cd ios
open TurboModuleExample.xcworkspace

如下图所示:

(2). 在 Xcode 工作区中,右键点击 app 目录,选择"新建组",将新组命名为NativeLocalStorage。

(3). 在 NativeLocalStorage组中,选择"新建=>从模板创建文件"。

(4). 使用 Cocoa Touch Class 模板创建新文件。

(5). 将 Cocoa Touch Class 命名为 RCTNativeLocalStorage,并选择 Objective-C 语言。

(6). 将 RCTNativeLocalStorage.m重命名为 RCTNativeLocalStorage.mm,使其成为一个 Objective-C++ 文件。

第二步:实现 localStorage,使用 NSUserDefaults。

首先更新 RCTNativeLocalStorage.h:

objectivec 复制代码
// NativeLocalStorage/RCTNativeLocalStorage.h
#import <Foundation/Foundation.h>
#import <NativeLocalStorageSpec/NativeLocalStorageSpec.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTNativeLocalStorage : NSObject <NativeLocalStorageSpec>

@end

NS_ASSUME_NONNULL_END

然后更新我们的实现,使用带有自定义套件名称的 NSUserDefaults:

objectivec 复制代码
// NativeLocalStorage/RCTNativeLocalStorage.mm
#import "RCTNativeLocalStorage.h"

static NSString *const RCTNativeLocalStorageKey = @"local-storage";

@interface RCTNativeLocalStorage()
@property (strong, nonatomic) NSUserDefaults *localStorage;
@end

@implementation RCTNativeLocalStorage

RCT_EXPORT_MODULE(NativeLocalStorage)

- (id)init {
    if (self = [super init]) {
        _localStorage = [[NSUserDefaults alloc] initWithSuiteName:RCTNativeLocalStorageKey];
    }
    return self;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
    return std::make_shared<facebook::react::NativeLocalStorageSpecJSI>(params);
}

- (NSString * _Nullable)getItem:(NSString *)key {
    return [self.localStorage stringForKey:key];
}

- (void)setItem:(NSString *)value key:(NSString *)key {
    [self.localStorage setObject:value forKey:key];
}

- (void)removeItem:(NSString *)key {
    [self.localStorage removeObjectForKey:key];
}

- (void)clear {
    NSDictionary *keys = [self.localStorage dictionaryRepresentation];
    for (NSString *key in keys) {
        [self removeItem:key];
    }
}

@end

重要事项:RCT_EXPORT_MODULE 导出并注册模块,使用我们在 JavaScript 环境中访问它的标识符:NativeLocalStorage。更多详细信息请查看相关文档。

你可以使用 Xcode 跳转到 Codegen 的 @protocol NativeLocalStorageSpec,也可以用 Xcode 生成存根。

在模拟器上构建并运行代码:

javascript 复制代码
yarn run ios

4.2. 原生模块开发与接入(Android)

React-Native 层的开发与 iOS 一致,我们从 Android 平台开发展开。

以下是针对 Android 部分的完整实现和优化:

现在,我们来实现 Android 平台代码,以确保 localStorage 在应用关闭后能够持久保存。

首先,实施生成的 NativeLocalStorageSpec 接口:

android/app/src/main/java/com/nativelocalstorage/NativeLocalStorageModule.java

java 复制代码
package com.nativelocalstorage;

import android.content.Context;
import android.content.SharedPreferences;
import com.nativelocalstorage.NativeLocalStorageSpec;
import com.facebook.react.bridge.ReactApplicationContext;

public class NativeLocalStorageModule extends NativeLocalStorageSpec {

    private static final String NAME = "NativeLocalStorage";

    public NativeLocalStorageModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public void setItem(String value, String key) {
        SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putString(key, value);
        editor.apply();
    }

    @Override
    public String getItem(String key) {
        SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
        return sharedPref.getString(key, null);
    }

    @Override
    public void removeItem(String key) {
        SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
        sharedPref.edit().remove(key).apply();
    }

    @Override
    public void clear() {
        SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
        sharedPref.edit().clear().apply();
    }
}

接下来,我们需要创建 NativeLocalStoragePackage,它将该模块注册到 React Native 运行时中,并将其包装为 Turbo Native Package:

android/app/src/main/java/com/nativelocalstorage/NativeLocalStoragePackage.java

java 复制代码
package com.nativelocalstorage;

import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;

import java.util.HashMap;
import java.util.Map;

public class NativeLocalStoragePackage extends TurboReactPackage {

    @Override
    public NativeModule getModule(String name, ReactApplicationContext reactContext) {
        if (name.equals(NativeLocalStorageModule.NAME)) {
            return new NativeLocalStorageModule(reactContext);
        } else {
            return null;
        }
    }

    @Override
    public ReactModuleInfoProvider getReactModuleInfoProvider() {
        return new ReactModuleInfoProvider() {
            @Override
            public Map<String, ReactModuleInfo> get() {
                Map<String, ReactModuleInfo> map = new HashMap<>();
                map.put(NativeLocalStorageModule.NAME, new ReactModuleInfo(
                    NativeLocalStorageModule.NAME,         // name
                    NativeLocalStorageModule.NAME,         // className
                    false,                                 // canOverrideExistingModule
                    false,                                 // needsEagerInit
                    false,                                 // isCXXModule
                    true                                   // isTurboModule
                ));
                return map;
            }
        };
    }
}

最后,我们需要在主应用中告诉 React Native 如何找到这个包,这称为在 React Native 中"注册"该包。

将其添加到 getPackages 方法中返回的列表中:

android/app/src/main/java/com/turbomoduleexample/MainApplication.java

java 复制代码
package com.turbomoduleexample;

import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactHost;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactHost;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import com.nativelocalstorage.NativeLocalStoragePackage;

import java.util.ArrayList;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost reactNativeHost = new DefaultReactNativeHost(this) {
    @Override
    public List<ReactPackage> getPackages() {
      List<ReactPackage> packages = new PackageList(this).getPackages();
      packages.add(new NativeLocalStoragePackage());
      return packages;
    }

    @Override
    public String getJSMainModuleName() {
      return "index";
    }

    @Override
    public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    public boolean isNewArchEnabled() {
      return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
    }

    @Override
    public boolean isHermesEnabled() {
      return BuildConfig.IS_HERMES_ENABLED;
    }
  };

  @Override
  public ReactHost getReactHost() {
    return DefaultReactHost.getDefaultReactHost(getApplicationContext(), reactNativeHost);
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, false);
    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      DefaultNewArchitectureEntryPoint.load();
    }
  }
}

现在,你可以在模拟器上构建并运行代码:

javascript 复制代码
yarn run android

5. 应用构建发布

在构建和发布 React Native 应用时,主要涉及到 iOS 和 Android 两个平台的特定配置和步骤。

以下是详细的内容,包括应用信息配置、图标配置、打包和发布的过程。

5.1. 应用信息配置

  1. iOS 配置
  • 项目设置:打开 Xcode,选择你的项目文件(.xcworkspace),在"General"选项卡中配置应用名称、版本、构建号和 Bundle Identifier。
  • info.plist: 修改 Info.plist文件,添加应用的描述、权限说明等。
html 复制代码
<key>CFBundleDisplayName</key>
<string>MyApp</string>
<key>CFBundleIdentifier</key>
<string>com.example.myapp</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
  • 启动页:配置 ios 项目下的 LaunchScreen。
  1. Android 配置
  • AndroidManifest.xml: 在 android/app/src/main/AndroidManifest.xml 中配置应用信息,如应用名称、图标、权限等。
XML 复制代码
<application
    android:name=".MainApplication"
    android:label="MyApp"
    android:icon="@mipmap/ic_launcher">
    ...
</application>
  • build.gradle: 在 android/app/build.gradle 文件中设置应用的版本和包名。
Kotlin 复制代码
android {
    ...
    defaultConfig {
        applicationId "com.example.myapp"
        versionCode 1
        versionName "1.0"
    }
}

5.2. 图标配置

图标的生成,可以使用 https://www.appicon.co/ 一键生成。

  1. iOS 图标
  • 图标资源:在 Xcode 的 Assets.xcassets 中,添加应用图标。确保按照 Apple 的图标规格提供不同尺寸的图标。
  1. Android 图标
  • 图标资源:在 android/app/src/main/res 目录下的各个 mipmap 文件夹中(如 mipmap-mdpi,mipmap-hdpi 等)放置不同尺寸的应用图标。

5.3. 打包

  1. iOS 打包
  • 使用 Xcode: 在 Xcode 中选择 Product > Archive 进行打包。

  • 导出: 打包完成后,会打开 Archives 窗口,选择最新的构建,点击 "Distribute App",根据提示选择导出方式(App Store、Ad Hoc 等)。

  1. Android 打包
  • 命令行打包: 在项目根目录下,运行以下命令生成 APK。
bash 复制代码
cd android
./gradlew assembleRelease
  • APK 文件: 打包完成后,APK 文件将位于 android/app/build/outputs/apk/release 目录中。

5.4. 发布

  1. iOS 发布
  • App Store Connect: 登录 App Store Connect,创建一个新的应用条目,并填写必要的信息。

  • 上传应用: 使用 Xcode 或者 Transporter 将生成的 .ipa 文件上传到 App Store Connect。

  • 审核: 提交审核,等待 Apple 的审核通过。

  1. Android 发布
  • Google Play Console: 登录 Google Play Console,创建一个新的应用。

  • 上传 APK: 在"版本"部分上传生成的 APK 文件,填写应用的描述、图标、截图等信息。

  • 发布: 提交应用,等待 Google 的审核通过。

6. 补充资料

相关推荐
aiguangyuan2 天前
Taro 开发快速入门手册
taro·前端开发·移动端开发
aiguangyuan4 天前
Taro多端适配技术解析
taro·前端开发·移动端开发
aiguangyuan5 天前
uni-app开发入门手册
uni-app·移动开发·前端开发
hunzhizi8 天前
2024-2025年技术发展趋势深度分析:AI、前端与后端开发的革新之路
微服务·前端开发·后端开发·ai开发·技术趋势·多模态ai
小九今天不码代码11 天前
巧用 CSS linear-gradient 实现多种下划线文字特效(纯 CSS 无需额外标签)
css·css3·前端开发·linear-gradient·前端特效·下划线样式·文字特效
IT技术分享社区14 天前
前端:浏览器Content Security Policy 安全策略介绍和用法
前端·前端开发
小九今天不码代码15 天前
深入理解 CSS 表格布局:table-layout 的秘密与实战详解(附费用报销单案例)
css·前端开发·表格布局·web设计·table-layout·页面优化·样式布局
流年染指悲伤、23 天前
2024年最新技术趋势分析:AI、前端与后端开发新动向
人工智能·前端开发·后端开发·2024·技术趋势
aiguangyuan1 个月前
微信小程序中的双线程模型及数据传输优化
微信小程序·前端开发