跳到主要内容
版本:0.86

在原生模块中发送事件

在某些情况下,你可能希望一个原生模块监听平台层中的某些事件,然后将它们发送到 JavaScript 层,让你的应用能够对这些原生事件做出响应。在其他情况下,你可能会有一些长时间运行的操作,它们可以发送事件,从而在这些操作进行时更新 UI。

这两种场景都是从原生模块中发送事件的很好用例。在本指南中,你将学习如何实现这一点。

在存储中添加新键时发送事件​

在这个示例中,你将学习如何在向存储添加新键时发送事件。修改键的值不会发送该事件,但添加新键会发送。

本指南从 原生模块 指南开始。 在深入学习本指南之前,请确保你已经熟悉那份指南,并且可能已经实现了其中的示例。

第 1 步:更新 NativeLocalStorage 的规范​

第一步是更新 NativeLocalStorage 的规范,以便让 React Native 知道该模块可以发送事件。

打开 NativeLocalStorage.ts 文件,并按如下方式更新:

NativeLocalStorage.ts
+import type {TurboModule, CodegenTypes} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

+export type KeyValuePair = {
+ key: string,
+ value: string,
+}

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

+ readonly onKeyAdded: CodegenTypes.EventEmitter<KeyValuePair>;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeLocalStorage',
);

通过 import type 语句,你从 react-native 导入了 CodegenTypes,其中包含 EventEmitter 类型。这使你可以使用 CodegenTypes.EventEmitter<KeyValuePair> 来定义 onKeyAdded 属性,指定该事件将发送一个类型为 KeyValuePair 的负载。

当事件被发送时,你期望它接收一个类型为 KeyValuePair 的参数。

第 2 步:生成 Codegen​

既然你已经更新了原生模块的规范,那么现在你需要重新运行 Codegen,以便在原生代码中生成相关产物。

这与原生模块指南中介绍的流程相同。

Codegen 通过 generateCodegenArtifactsFromSchema Gradle 任务执行:

cd android
./gradlew generateCodegenArtifactsFromSchema

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

当你构建 Android 应用时,这个步骤会自动运行。

第 3 步:更新应用代码​

现在,是时候更新应用代码来处理这个新事件了。

打开 App.tsx 文件,并按如下方式修改:

App.tsx
import {
+ Alert,
+ EventSubscription,
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 [key, setKey] = React.useState<string | null>(null);
+ const listenerSubscription = React.useRef<null | EventSubscription>(null);

+ React.useEffect(() => {
+ listenerSubscription.current = NativeLocalStorage?.onKeyAdded((pair) => Alert.alert(`New key added: ${pair.key} with value: ${pair.value}`));

+ return () => {
+ listenerSubscription.current?.remove();
+ listenerSubscription.current = null;
+ }
+ }, [])

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

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

function saveValue() {
+ if (key == null) {
+ Alert.alert('Please enter a key');
+ return;
+ }
NativeLocalStorage?.setItem(editingValue ?? EMPTY, key);
setValue(editingValue);
}

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

function deleteValue() {
+ if (key == null) {
+ Alert.alert('Please enter a key');
+ return;
+ }
NativeLocalStorage?.removeItem(key);
setValue('');
}

+ function retrieveValue() {
+ if (key == null) {
+ Alert.alert('Please enter a key');
+ return;
+ }
+ const val = NativeLocalStorage?.getItem(key);
+ setValue(val);
+ }

return (
<SafeAreaView style={{flex: 1}}>
<Text style={styles.text}>
Current stored value is: {value ?? 'No Value'}
</Text>
+ <Text>Key:</Text>
+ <TextInput
+ placeholder="输入你想存储的键"
+ style={styles.textInput}
+ onChangeText={setKey}
+ />
+ <Text>Value:</Text>
<TextInput
- placeholder="Enter the text you want to store"
+ placeholder="输入你想存储的文本"
style={styles.textInput}
onChangeText={setEditingValue}
/>
- <Button title="Save" onPress={saveValue} />
+ <Button title="Save" onPress={saveValue} />
+ <Button title="Retrieve" onPress={retrieveValue} />
<Button title="Delete" onPress={deleteValue} />
<Button title="Clear" 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. 你需要从 react-native 导入 EventSubscription 类型来处理 EventSubscription
  2. 你需要使用 useRef 来跟踪 EventSubscription 引用
  3. 你使用 useEffect 钩子注册监听器。onKeyAdded 函数接收一个回调,该回调参数是一个类型为 KeyValuePair 的对象
  4. 添加到 onKeyAdded 的回调会在每次事件从 Native 发送到 JS 时执行
  5. 在 useEffect 的清理函数中,你需要 remove 事件订阅,并将 ref 设为 null

其余改动都是常规的 React 修改,用于改进这个新功能的应用。

第 4 步:编写你的原生代码​

一切准备就绪后,让我们开始编写原生平台代码。

假设你已经按照 原生模块指南 中描述的 Android 指南完成了操作,那么接下来要做的就是把发送事件的代码接入到你的应用中。

为此,你需要:

  1. 打开 NativeLocalStorage.kt 文件
  2. 按如下方式修改它:
NativeLocalStorage
package com.nativelocalstorage

import android.content.Context
import android.content.SharedPreferences
import com.nativelocalstorage.NativeLocalStorageSpec
+import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.WritableMap

class NativeLocalStorageModule(reactContext: ReactApplicationContext) : NativeLocalStorageSpec(reactContext) {

override fun getName() = NAME

override fun setItem(value: String, key: String) {
+ var shouldEmit = false
+ if (getItem(key) != null) {
+ shouldEmit = true
+ }
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putString(key, value)
editor.apply()

+ if (shouldEmit == true) {
+ val eventData = Arguments.createMap().apply {
+ putString("key", key)
+ putString("value", value)
+ }
+ emitOnKeyAdded(eventData)
+ }
}

override fun getItem(key: String): String? {
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val username = sharedPref.getString(key, null)
return username.toString()
}

首先,你需要导入几个类型,用于创建需要从 Native 发送到 JS 的 eventData。这些导入是:

  • import com.facebook.react.bridge.Arguments
  • import com.facebook.react.bridge.WritableMap

其次,你需要实现实际将事件发送到 JS 的逻辑。对于复杂类型,比如规范中定义的 KeyValuePair,Codegen 会生成一个期望以 ReadableMap 作为参数的函数。你可以通过使用 Arguments.createMap() 工厂方法创建 ReadableMap,并使用 apply 函数来填充这个 map。你有责任确保在 map 中使用的键与 JS 中规范类型里定义的属性相同。

第 5 步:运行你的应用​

如果你现在尝试运行你的应用,你应该会看到这种行为。

Android
iOS