代码在这里

很多时候咱们须要从原生发送事件给JS。比方在官网文档提到的一个日历事件。你定好了一个会议,或者一个流动,之后再指定的日期产生。或者敞开了奉献单车,蓝牙收到关锁胜利的信号。又或者天文围栏这样的APP,在你进入/来到一个天文围栏的时候,都须要从原生发送事件给JS。

首先是一个简略的例子

调用一个原生办法设置一个延时触发的原生工夫,相似于调用原生的setTimeout。在到工夫之后一个事件会从原生发送到JS。

首先UI上会有一个能够输出工夫的文本框,在用户输出了工夫并点击了OK按钮之后。App就会调用原生办法执行原生的setTimeout办法。

App调用的原生办法是一个在前端的promise办法。所以,这个办法能够用async-await调用。

在JS的局部会注册一个事件的监听器,一但收到原生事件就会执行JS代码。在本例中为了简略只是输入了一条log。

既然是从原生接管和发送事件,那么一个原生的模块是必不可少的。还不是很理解这部分的同学能够移步到这是iOS的,这是Android的

一点须要更新的是,当初官网举荐在实现Android原生模块的时候应用`ReactContextBaseJavaModule`。次要是出于类型平安的思考。

在iOS实现一个原生模块

// header file@interface FillingHoleModule: RCTEventEmitter<RCTBridgeModule>@end// implementation#import "FillingHoleModule.h"@implementation FillingHoleModuleRCT_EXPORT_MODULE(FillingHoleModule)RCT_EXPORT_METHOD(sendEventInSeconds: (NSUInteger) seconds resolver:(RCTPromiseResolveBlock) resolverejecter: (RCTPromiseRejectBlock) reject) {  @try {    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * seconds);    dispatch_after(delay, dispatch_get_main_queue(), ^(void) {      [self sendEventWithName:@"FillingHole" body:@{@"filling": @"hole", @"with": @"RN"}];    });        NSLog(@"Resolved");    resolve(@"done");  } @catch (NSException *exception) {    NSLog(@"Rejected %@", exception);    reject(@"Failed", @"Cannot setup timeout", nil);  }}- (NSArray<NSString *> *)supportedEvents {  return @[@"FillingHole"];}@end
这里省略了模块头文件。

1: 在头文件里能够看到原生模块继承了RCTEventEmitter

2: 所以在实现的时候须要实现这个类的办法supportedEvents。就是把咱们要从原生发送的事件的名字加进去。如:

- (NSArray<NSString *> *)supportedEvents {  return @[@"FillingHole"];}

3: 在办法sendEventInSeconds里的try-catch能够辅助调用resolvereject正好实现了JS局部的promise

4:这部分在objc里相当于setTimeout:

    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * seconds);    dispatch_after(delay, dispatch_get_main_queue(), ^(void) {      [self sendEventWithName:@"FillingHole" body:@{@"filling": @"hole", @"with": @"RN"}];    });

代码[self sendEventWithName:@"FillingHole" body:@{@"filling": @"hole", @"with": @"RN"}];实现了从原生到JS发送事件的性能。

在Android的原生模块

public class FillingEventHole extends ReactContextBaseJavaModule {    FillingEventHole(ReactApplicationContext context) {        super(context);    }    @NonNull    @Override    public String getName() {        return "FillingHoleModule";    }    @ReactMethod    public void sendEventInSeconds(long seconds, Promise promise) {        Log.d("FillEventHole", "Event from native comes in" + seconds);        try {            new android.os.Handler(Looper.getMainLooper()).postDelayed(new Runnable() {                @Override                public void run() {                    WritableMap params = Arguments.createMap();                    params.putString("filling", "hole");                    params.putString("with", "RN");                    FillingEventHole.this.sendEvent("FillingHole", params);                }            }, seconds * 1000);            promise.resolve("Done");        } catch (Exception e) {            promise.reject(e);        }    }    private void sendEvent(String eventName, @Nullable WritableMap params) {        this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);    }}

留神:具体原生的局部步骤比iOS的多一些。这里就都省略了,如果须要参考上文提到的Android原生模块局部。

1sendEventInSeconds用于接管JS传递的工夫信息,并开始Android这里的setTimeout
2:try-catch局部和iOS的一样,配合解决resolve和reject。
3:Android的setTimeout

    new android.os.Handler(Looper.getMainLooper()).postDelayed(new Runnable() {        @Override        public void run() {            WritableMap params = Arguments.createMap();            params.putString("filling", "hole");            params.putString("with", "RN");            FillingEventHole.this.sendEvent("FillingHole", params);        }    }, seconds * 1000);

4: 办法FillingEventHole.this.sendEvent("FillingHole", params);调用在原生模块里实现的setEvent办法来发送事件。
5:事件发送办法:

    private void sendEvent(String eventName, @Nullable WritableMap params) {        this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);    }

前端实现

在开始前端的局部之前,先说几句废话。

Android和iOS实现的原生模块必须是同名的。模块名要同名,原生办法也要同名。在前端调用原生办法的时候须要保障类型是对应的。比方上例的原生办法public void sendEventInSeconds(long seconds, Promise promise)工夫参数是数字类型的,那么在JS调用的时候就必须是数字类型的,否则就会出错。或者,你能够提供定制的类型转化办法。

在前端只须要:

import {  //...  NativeEventEmitter,  NativeModules,  EmitterSubscription,} from 'react-native';const {FillingHoleModule} = NativeModules;  // 1const eventEmitter = new NativeEventEmitter(FillingHoleModule); // 2const App = () => {  useEffect(() => {    // 3    const eventListener = eventEmitter.addListener('FillingHole', event => {      console.log('You received an event', JSON.stringify(event));    });    listenersRef.current = eventListener;    return () => {      // 4      listenersRef.current?.remove();    };  });  // 5  const handlePress = async () => {    console.log('>', text);    try {      await FillingHoleModule.sendEventInSeconds(+text);    } catch (e) {      console.error('Create event failed, ', e);    }  };  return (    //...  );}

1:从NativeModules拿到定义的原生模块
2:应用定义好的原生模块初始化NativeEventEmiotter
3:增加原生事件的listener
4:在最初销毁listener
5:在用户点击按钮的时候调用原生模块裸露的办法登程原生事件的setTimeout办法。

3>和4>都是react hooks的内容,不熟的同学能够参考官网。

这里还有一个实用的小技巧,应用useRef保留了组件事件的listener。

最初

筹备工作都做好了之后就能够跑起来。摇一摇手机开启debug模式来查看从原生接管的事件了。

从原生收回事件用到的中央不肯定多,然而必定是一个十分有用的性能点。心愿这边文章能够帮到你。