跳到主要内容

在原生模块中发送事件

在某些情况下,你可能希望一个原生模块监听平台层中的某些事件,然后将它们发送到 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