在原生模块中发出事件
在某些情况下,您可能希望有一个原生模块,它可以监听平台层中的某些事件,然后将它们发送到 JavaScript 层,以便您的应用程序可以对这些原生事件做出反应。在其他情况下,您可能有需要发出事件的长时间运行的操作,以便在这些操作发生时可以更新 UI。
这两种情况都是从原生模块发出事件的良好用例。在本指南中,您将学习如何做到这一点。
当新键添加到存储时发出事件
在此示例中,您将学习如何在向存储添加新键时发出事件。更改键的值不会发出事件,但添加新键会发出事件。
本指南从原生模块指南开始。在深入研究本指南之前,请务必熟悉该指南,并可能实现指南中的示例。
步骤 1:更新 NativeLocalStorage 的规范
第一步是更新 NativeLocalStorage
的规范,让 React Native 知道该模块可以发出事件。
- TypeScript
- Flow
打开 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',
);
打开 NativeLocalStorage.js
文件并按如下方式更新它
// @flow
import type {TurboModule} from 'react-native';
import {TurboModule, 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;
removeItem(key: string): void;
clear(): void;
+ onKeyAdded: EventEmitter<KeyValuePair>
}
export default (TurboModuleRegistry.get<Spec>(
'NativeLocalStorage'
): ?Spec);
通过 import type
语句,您正在导入 EventEmitter
类型,该类型是添加 onKeyAdded
属性所必需的。
当事件发出时,您期望它接收一个 string
类型的参数。
步骤 2:生成 Codegen
鉴于您已更新原生模块的规范,现在必须重新运行 Codegen 以在原生代码中生成工件。
这与原生模块指南中介绍的过程相同。
- Android
- iOS
Codegen 通过 generateCodegenArtifactsFromSchema
Gradle 任务执行
cd android
./gradlew generateCodegenArtifactsFromSchema
BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date
当您构建 Android 应用程序时,这会自动运行。
Codegen 作为脚本阶段的一部分运行,该脚本阶段会自动添加到 CocoaPods 生成的项目中。
cd ios
bundle install
bundle exec pod install
输出将如下所示
...
Framework build type is static library
[Codegen] Adding script_phases to ReactCodegen.
[Codegen] Generating ./build/generated/ios/ReactCodegen.podspec.json
[Codegen] Analyzing /Users/me/src/TurboModuleExample/package.json
[Codegen] Searching for Codegen-enabled libraries in the app.
[Codegen] Found TurboModuleExample
[Codegen] Searching for Codegen-enabled libraries in the project dependencies.
[Codegen] Found react-native
...
步骤 3:更新 App 代码
现在,是时候更新 App 的代码来处理新事件了。
打开 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;
有几个相关的更改需要注意
- 您需要从
react-native
导入EventSubscription
类型来处理EventSubscription
- 您需要使用
useRef
来跟踪EventSubscription
引用 - 您使用
useEffect
钩子注册监听器。onKeyAdded
函数将一个KeyValuePair
类型的对象作为函数参数。 - 添加到
onKeyAdded
的回调函数在每次事件从 Native 发出到 JS 时都会执行。 - 在
useEffect
清理函数中,您remove
事件订阅并将 ref 设置为null
。
其余更改是常规 React 更改,用于改进此新功能的 App。
步骤 4:编写您的原生代码
一切准备就绪,让我们开始编写原生平台代码。
- Android
- iOS
假设您遵循了原生模块指南中描述的 iOS 指南,剩下要做的就是将发出事件的代码插入到您的应用程序中。
为此,您必须
- 打开
NativeLocalStorage.kt
文件 - 按如下方式修改它
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 中规范类型中定义的属性相同。
假设您遵循了原生模块指南中描述的 iOS 指南,剩下要做的就是将发出事件的代码插入到您的应用程序中。
为此,您必须
- 打开
RCTNativeLocalStorage.h
文件。 - 将基类从
NSObject
更改为NativeLocalStorageSpecBase
#import <Foundation/Foundation.h>
#import <NativeLocalStorageSpec/NativeLocalStorageSpec.h>
NS_ASSUME_NONNULL_BEGIN
-@interface RCTNativeLocalStorage : NSObject <NativeLocalStorageSpec>
+@interface RCTNativeLocalStorage : NativeLocalStorageSpecBase <NativeLocalStorageSpec>
@end
NS_ASSUME_NONNULL_END
- 打开
RCTNativeLocalStorage.mm
文件。 - 根据需要修改它以发出事件,例如
- (void)setItem:(NSString *)value key:(NSString *)key {
+ BOOL shouldEmitEvent = NO;
+ if (![self getItem:key]) {
+ shouldEmitEvent = YES;
+ }
[self.localStorage setObject:value forKey:key];
+ if (shouldEmitEvent) {
+ [self emitOnKeyAdded:@{@"key": key, @"value": value}];
+ }
}
NativeLocalStorageSpecBase
是一个基类,它提供了 emitOnKeyAdded
方法及其基本实现和样板代码。多亏了这个类,您不必处理将事件发送到 JS 所需的 Objective-C 和 JSI 之间的所有转换。
对于复杂类型,例如规范中定义的 KeyValuePair
,Codegen 将生成一个通用字典,您可以在原生端填充它。您有责任确保在字典中使用的键与 JS 中规范类型中定义的属性相同。
步骤 5:运行您的应用程序
如果您现在尝试运行您的应用程序,您应该会看到此行为。
![]() | ![]() |