跳到主要内容

在原生模块中发出事件

在某些情况下,您可能希望有一个原生模块,它可以监听平台层中的某些事件,然后将它们发送到 JavaScript 层,以便您的应用程序可以对这些原生事件做出反应。在其他情况下,您可能有需要发出事件的长时间运行的操作,以便在这些操作发生时可以更新 UI。

这两种情况都是从原生模块发出事件的良好用例。在本指南中,您将学习如何做到这一点。

当新键添加到存储时发出事件

在此示例中,您将学习如何在向存储添加新键时发出事件。更改键的值不会发出事件,但添加新键会发出事件。

本指南从原生模块指南开始。在深入研究本指南之前,请务必熟悉该指南,并可能实现指南中的示例。

步骤 1:更新 NativeLocalStorage 的规范

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

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

NativeLocalStorage.ts
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
+import type {EventEmitter} from 'react-native/Libraries/Types/CodegenTypes';

+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: EventEmitter<KeyValuePair>;
}

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

通过 import type 语句,您正在导入 EventEmitter 类型,该类型是添加 onKeyAdded 属性所必需的。

当事件发出时,您期望它接收一个 string 类型的参数。

步骤 2:生成 Codegen

鉴于您已更新原生模块的规范,现在必须重新运行 Codegen 以在原生代码中生成工件。

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

Codegen 通过 generateCodegenArtifactsFromSchema Gradle 任务执行

bash
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 钩子注册监听器。onKeyAdded 函数将一个 KeyValuePair 类型的对象作为函数参数。
  4. 添加到 onKeyAdded 的回调函数在每次事件从 Native 发出到 JS 时都会执行。
  5. useEffect 清理函数中,您 remove 事件订阅并将 ref 设置为 null

其余更改是常规 React 更改,用于改进此新功能的 App。

步骤 4:编写您的原生代码

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

假设您遵循了原生模块指南中描述的 iOS 指南,剩下要做的就是将发出事件的代码插入到您的应用程序中。

为此,您必须

  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 函数填充映射。您有责任确保在映射中使用的键与 JS 中规范类型中定义的属性相同。

步骤 5:运行您的应用程序

如果您现在尝试运行您的应用程序,您应该会看到此行为。

Android
iOS