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. 工程化说明
- 项目结构
创建项目后,我们优化目录结构,如下:
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 入口文件
- 重要文件与目录
-
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 应用的入口文件,通常用于注册主组件并启动应用。
- 项目配置
-
依赖管理: 使用 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 提供了多种内置组件,方便开发者快速构建用户界面。以下是一些常用组件及其用法。
- View
用法:View 是 React Native 中最基本的组件,用于构建 UI 的布局。它可以容纳其他组件,并支持样式和事件处理。这个组件可以当做 div 用。
javascript
import { View } from 'react-native';
const MyComponent = () => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/* 其他组件 */}
</View>
);
};
- Text
用法:Text 组件用于显示文本。支持多种文本样式,如字体、颜色、大小等。
javascript
import { Text } from 'react-native';
const MyText = () => {
return (
<Text style={{ fontSize: 20, color: 'blue' }}>
Hello, React Native!
</Text>
);
};
- Image
用法:Image 组件用于显示图片,支持本地图片和网络图片。
javascript
import { Image } from 'react-native';
const MyImage = () => {
return (
<Image
source={{ uri: 'https://example.com/image.png' }}
style={{ width: 100, height: 100 }}
/>
);
};
- 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>
);
};
- TextInput
用法:TextInput 组件用于接收用户输入。可以设置占位符、样式等属性。
javascript
import { TextInput } from 'react-native';
const MyTextInput = () => {
return (
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
placeholder="Type here"
/>
);
};
- Button
用法:Button 组件用于创建按钮,支持点击事件。
javascript
import { Button } from 'react-native';
const MyButton = () => {
return (
<Button
title="Click Me"
onPress={() => alert('Button pressed!')}
/>
);
};
- 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}
/>
);
};
- TouchableOpacity
用法:TouchableOpacity 组件用于实现可点击的元素,支持透明度变化。
javascript
import { TouchableOpacity, Text } from 'react-native';
const MyTouchable = () => {
return (
<TouchableOpacity onPress={() => alert('Tapped!')}>
<Text style={{ fontSize: 20 }}>Tap Me</Text>
</TouchableOpacity>
);
};
- 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>
);
};
- SafeAreaView
用法:用于给页面给出安全区域的处理
javascript
<SafeAreaView style={backgroundStyle}>
<StatusBar />
</SafeAreaView>
2.2. 样式表
在 React Native 中,样式表的管理和应用非常重要。
React Native 提供了 StyleSheet API,方便开发者创建和管理组件的样式。以下是对 StyleSheet API 的详细介绍,包括主题的实现方案和 StyleSheet 的原理。
- 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). 使用 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>
);
};
- 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 库,可以轻松地创建和管理不同的屏幕和导航结构。以下是对你提供的代码示例的详细讲解。
- 项目依赖
在 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: 提供安全区域支持,确保在刘海屏或其他特殊屏幕形状下的内容显示正确。
- 创建底部标签导航
在 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 表示在底部标签导航中不显示头部。
- 创建堆栈导航
接下来,创建一个堆栈导航组件,用于管理更复杂的导航结构:
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 打开。
- 路由处理过程
-
导航结构:在此示例中,定义了一个底部标签导航(BottomBar)和多个堆栈屏幕(Home、Details、Topics)。用户可以在底部标签之间切换,同时也可以通过堆栈导航访问详细信息和主题。
-
屏幕的渲染:当用户切换到某个标签时,BottomBar 中的对应组件会被渲染。在这个底部导航中,HomeScreen 和 DetailScreen 是两个标签页。
-
堆栈导航:如果在 HomeScreen 中,我们希望导航到 DetailScreen,可以使用 navigation.navigate('Details') 方法来实现。这将把 DetailScreen 推入堆栈中,并自动管理返回操作。
-
状态管理:在导航中,React Navigation 会自动管理导航状态,例如当前活动的屏幕、参数传递等。每当导航状态变化时,相关的组件会重新渲染以反映新的状态。
- 主题和样式
在 Navigator 函数中,你还通过 screenOptions 为导航器设置了一些样式和行为选项:
-
headerTitleAlign: 设置头部标题的对齐方式。
-
headerBackTitleVisible: 是否在返回按钮上显示标题。
-
gestureEnabled: 是否启用手势返回功能。
-
headerStyle: 设置头部的样式,例如背景颜色。
2.4. 常用API
- 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) {
// 读取错误
}
};
- 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);
}
};
- 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}`);
};
- 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}`);
};
- 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);
}
};
- Vibration
控制设备震动。
javascript
import { Vibration } from 'react-native';
// 震动1000毫秒
const triggerVibration = () => {
Vibration.vibrate(1000);
};
- 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);
}
};
- 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
- 声明类型规范
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;
- 配置 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
...
- 使用 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;
- 编写原生平台代码
准备好后,我们将开始编写原生平台代码。这个过程分为两个部分:
注意:本指南展示的是如何创建一个仅适用于新架构的 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. 应用信息配置
- 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。

- 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/ 一键生成。
- iOS 图标
- 图标资源:在 Xcode 的 Assets.xcassets 中,添加应用图标。确保按照 Apple 的图标规格提供不同尺寸的图标。

- Android 图标
- 图标资源:在 android/app/src/main/res 目录下的各个 mipmap 文件夹中(如 mipmap-mdpi,mipmap-hdpi 等)放置不同尺寸的应用图标。
5.3. 打包
- iOS 打包
-
使用 Xcode: 在 Xcode 中选择 Product > Archive 进行打包。
-
导出: 打包完成后,会打开 Archives 窗口,选择最新的构建,点击 "Distribute App",根据提示选择导出方式(App Store、Ad Hoc 等)。
- Android 打包
- 命令行打包: 在项目根目录下,运行以下命令生成 APK。
bash
cd android
./gradlew assembleRelease
- APK 文件: 打包完成后,APK 文件将位于 android/app/build/outputs/apk/release 目录中。
5.4. 发布
- iOS 发布
-
App Store Connect: 登录 App Store Connect,创建一个新的应用条目,并填写必要的信息。
-
上传应用: 使用 Xcode 或者 Transporter 将生成的 .ipa 文件上传到 App Store Connect。
-
审核: 提交审核,等待 Apple 的审核通过。
- Android 发布
-
Google Play Console: 登录 Google Play Console,创建一个新的应用。
-
上传 APK: 在"版本"部分上传生成的 APK 文件,填写应用的描述、图标、截图等信息。
-
发布: 提交应用,等待 Google 的审核通过。
6. 补充资料
-
React-Native 官方文档:https://reactnative.dev/docs/getting-started
-
React-Native 导航库:https://reactnavigation.org/docs/getting-started
-
React-Native 样式表:https://reactnative.dev/docs/stylesheet
-
Material Design UI 库:https://callstack.github.io/react-native-paper/
-
基于 Tailwind 的 UI 库:https://gluestack.io/
-
小清新 UI 库:https://tamagui.dev/
-
构建分发平台:https://www.pgyer.com/
-
老架构下的原生模块开发与集成:https://reactnative.dev/docs/legacy/native-modules-intro
-
新架构下的原生模块开发与集成:https://reactnative.dev/docs/turbo-native-modules-introduction
-
Turbo Native Module 架构:https://github.com/reactwg/react-native-new-architecture/blob/main/docs/turbo-modules.md
-
Fabric Native Components 架构:https://github.com/reactwg/react-native-new-architecture/blob/main/docs/fabric-native-components.md
-
React-Native 新架构:https://reactnative.dev/architecture/landing-page
-
优化 js 引擎:https://github.com/facebook/hermes