跳到主要内容
版本:0.80

在原生模块中发出事件

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

现在,是时候更新 App 的代码以处理新事件了。

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

App.tsx
import React from 'react';
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="Enter the key you want to store"
+ style={styles.textInput}
+ onChangeText={setKey}
+ />
+ <Text>Value:</Text>
<TextInput
placeholder="Enter the text you want to store"
style={styles.textInput}
onChangeText={setEditingValue}
/>
<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 hook 注册监听器。onKeyAdded 函数接受一个回调,该回调以 KeyValuePair 类型的对象作为函数参数。
  4. 添加到 onKeyAdded 的回调每次事件从原生端发出到 JS 时都会执行。
  5. useEffect 清理函数中,你 remove 事件订阅并将 ref 设置为 null

其余的更改是常规的 React 更改,以改进 App 以适应此新功能。

步骤 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()
}

首先,你需要导入一些类型,你需要使用它们来创建需要从原生端发送到 JS 的 eventData。这些导入是:

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

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

步骤 5:运行你的应用

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

Android
iOS