跳到主要内容

在原生模块中发出事件

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

当事件发出时,您期望它接收一个 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.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. 每次从原生到 JS 发出事件时,都会执行添加到 onKeyAdded 的回调。
  5. useEffect 清理函数中,您 remove 事件订阅并将 ref 设置为 null

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

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

首先,您需要导入几个您需要用于创建从原生发送到 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