跳到主要内容
版本:0.82

原生模块

你的 React Native 应用代码可能需要与原生平台 API 交互,而这些 API 可能并未由 React Native 或现有库提供。你可以使用 Turbo 原生模块 自行编写集成代码。本指南将向你展示如何编写它。

基本步骤如下:

  1. 使用最流行的 JavaScript 类型注解语言之一:Flow 或 TypeScript,定义一个带类型的 JavaScript 规范
  2. 配置你的依赖管理系统以运行 Codegen,它会将该规范转换为原生语言接口;
  3. 使用你的规范编写应用代码;以及
  4. 使用生成的接口编写你的原生平台代码,将原生代码编写并接入 React Native 运行时环境。

我们将通过构建一个示例 Turbo 原生模块来逐步完成这些步骤。本指南的其余部分假设你已经使用以下命令创建了你的应用:

shell
npx @react-native-community/cli@latest init TurboModuleExample --version 0.82

原生持久化存储

本指南将展示如何实现 Web Storage APIlocalStorage。这个 API 对于正在项目中编写应用代码的 React 开发者来说非常熟悉。

要在移动端实现这一点,我们需要使用 Android 和 iOS 的 API:

1. 声明带类型的规范

React Native 提供了一个名为 Codegen 的工具,它接受用 TypeScript 或 Flow 编写的规范,并为 Android 和 iOS 生成平台特定的代码。该规范声明了将在你的原生代码与 React Native JavaScript 运行时之间来回传递的方法和数据类型。Turbo 原生模块既包括你的规范、你编写的原生代码,也包括由你的规范生成的 Codegen 接口。

要创建规范文件:

  1. 在应用的根目录中,创建一个名为 specs 的新文件夹。
  2. 创建一个名为 NativeLocalStorage.ts 的新文件。
信息

你可以在 附录 文档中查看你可以在规范中使用的所有类型,以及会生成的原生类型。

信息

如果你想更改模块名称及其相关规范文件的名称,请确保始终使用 'Native' 作为前缀(例如 NativeStorageNativeUsersDefault)。

下面是 localStorage 规范的一个实现:

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',
);

2. 配置 Codegen 运行

该规范会被 React Native Codegen 工具用于为我们生成平台特定的接口和样板代码。为此,Codegen 需要知道到哪里查找我们的规范,以及如何处理它。请在你的 package.json 中添加:

package.json
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "NativeLocalStorageSpec",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.nativelocalstorage"
}
},
"dependencies": {

在一切都为 Codegen 接好后,我们需要准备原生代码,以便接入生成的代码。

Codegen 通过 generateCodegenArtifactsFromSchema Gradle 任务执行:

cd android
./gradlew generateCodegenArtifactsFromSchema

BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date

这会在你构建 Android 应用时自动运行。

3. 使用 Turbo 原生模块编写应用代码

使用 NativeLocalStorage,下面是一个修改后的 App.tsx,其中包含我们希望持久化的一些文本、一个输入框和一些用于更新该值的按钮。

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

  • get<T>(name: string): T | null:如果 Turbo 原生模块不可用,则返回 null
  • getEnforcing<T>(name: string): T:如果 Turbo 原生模块不可用,则抛出异常。它假设该模块始终可用。
App.tsx
import {useEffect, useState, type JSX} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
TextInput,
Button,
} from 'react-native';

import NativeLocalStorage from './specs/NativeLocalStorage';

const EMPTY = '<empty>';

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

const [editingValue, setEditingValue] = useState<string | null>(
null,
);

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('myKey');
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;

4. 编写你的原生平台代码

一切准备就绪后,我们将开始编写原生平台代码。我们分两部分进行:

备注

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

现在是时候编写一些 Android 平台代码,以确保 localStorage 在应用程序关闭后依然存在。

第一步是实现生成的 NativeLocalStorageSpec 接口:

android/app/src/main/java/com/nativelocalstorage/NativeLocalStorageModule.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 {

public 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);
String username = sharedPref.getString(key, null);
return username;
}

@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。它通过将我们的模块包装为 Base Native Package,提供一个对象以便在 React Native 运行时中注册我们的模块:

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

import com.facebook.react.BaseReactPackage;
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 BaseReactPackage {

@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> getReactModuleInfos() {
Map<String, ReactModuleInfo> map = new HashMap<>();
map.put(NativeLocalStorageModule.NAME, new ReactModuleInfo(
NativeLocalStorageModule.NAME, // 名称
NativeLocalStorageModule.NAME, // 类名
false, // 可以覆盖现有模块
false, // 需要急切初始化
false, // 是 CXX 模块
true // 是 Turbo 模块
));
return map;
}
};
}
}

最后,我们需要告诉主应用程序中的 React Native 如何找到这个 Package。我们称之为在 React Native 中“注册”包。

在这种情况下,你将其添加到由 getPackages 方法返回的内容中。

信息

之后你会了解如何将你的原生模块作为 npm 包 分发,我们的构建工具会自动为你进行链接。

android/app/src/main/java/com/turobmoduleexample/MainApplication.java
package com.inappmodule;

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 MyReactNativePackage());
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();
}
}
}

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

npm run android