【React Native入门教程】原生模块

前言

RN是一个跨平台的架构,一套代码可以运行在iOS和Android两端,然而当涉及调用原生API时,不同平台会有差异性,例如蓝牙操作等。RN通过NativeModuleJava/Objective-C/C++ (native) 等原生类映射为JS对象,从而达到调用原生方法的目的。

NativeModule可以帮助你复用现有的原生方法,或者通过原生实现一些高性能,多线程的功能。

区分平台

很多时候我们会的代码会需要区分平台,例如一个按钮在iOS上显示蓝色,在Android上显示红色。RN提供两种方式用于区分平台。

  • 通过platform模块
  • 使用平台相关的文件拓展名

platform模块

import {Platform, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
  height: Platform.OS === 'ios' ? 200 : 100,
});

Platform.OS包含了当前平台信息可选值为ios 和 android

import {Platform, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {
        backgroundColor: 'red',
      },
      android: {
        backgroundColor: 'green',
      },
      default: {
        // other platforms, web for example
        backgroundColor: 'blue',
      },
    }),
  },
});

Platform.select会根据当前系统返回不同的对象

import {Platform} from 'react-native';

if (Platform.Version === 25) {
  console.log('Running on Nougat!');
}


const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 9) {
  console.log('Work around a change in behavior');
}

Platform.Version返回当前平台版本,注意ios需要通过parseInt(Platform.Version, 10)转换一下

平台相关文件拓展名

通过 .ios..android. 拓展名可以定义区分平台的文件,例如

BigButton.ios.js
BigButton.android.js

这里定义的两个同名组件,当运行平台为iOS时,则加载BigButton.ios.js

Android NativeModule使用

我们通过RN调用原生日历API创建日历事件这个案例来熟悉如何使用NativeModule

  1. 在Android原生项目中新建文件CalendarModule.kt,路径为android/app/src/main/java/com/your-app-name/
package com.your-apps-package-name; // replace your-apps-package-name with your app’s package name
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class CalendarModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    // add to CalendarModule.kt
    override fun getName() = "CalendarModule"
    
}

对于提供给RN的类,需要继承ReactContextBaseJavaModule,并且实现方法getName返回模块名。getName返回的名称也是RN调用时的名称

//JS 引入NativeModule
const {CalendarModule} = NativeModules?.CalendarModule;
  1. 通过@ReactMethod注解定义供RN使用的方法
import android.util.Log

//@ReactMethod(isBlockingSynchronousMethod = true)
@ReactMethod
fun createCalendarEvent(name: String, location: String) {
    Log.d("CalendarModule", "Create event called with name: $name and location: $location")
}

@ReactMethod注解可通过isBlockingSynchronousMethod 参数定义此函数是否加锁。定义好函数以后,RN可通过如下方式调用

NativeModules?.CalendarModule?.createCalendarEvent(name, location)
  1. 注册模块,定义好的类需要通过ReactPackage注册才可以使用
package com.your-app-name // replace your-app-name with your app’s name

import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager

class MyAppPackage : ReactPackage {

    override fun createViewManagers(
        reactContext: ReactApplicationContext
    ): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()

    override fun createNativeModules(
        reactContext: ReactApplicationContext
    ): MutableList<NativeModule> = listOf(CalendarModule(reactContext)).toMutableList()
}

首先新建一个类继承ReactPackage,然后在createNativeModules方法中返回我们定义的模块,这里是可以返回一个列表,也就是说我们可以定义多个模块同时返回。ReactPackage又该何时使用呢?

override fun getPackages(): List<ReactPackage> =
    PackageList(this).packages.apply {
        // Packages that cannot be autolinked yet can be added manually here, for example:
        // packages.add(new MyReactNativePackage());
        add(MyAppPackage())
    }

ReactNativeHost下有一个getPackages方法, 在这里返回即可

ReactNativeHost 是 ReactApplication中必须通过getReactNativeHost返回的参数,也就是ReactNative在原生部分的开始。

到这里整个创建注册NativeModule流程已经结束。

参数转换

在调用原生方法时,JS的数据类型和JAVA/Kotlin数据类型之间要进行相互转换,转换方式如下:
在这里插入图片描述
对于以上没有定义的参数,需要自行转换。

NativeModule进阶

导出常量

ReactContextBaseJavaModule还有一个可复写的函数getConstants,此函数返回Map,我们可以在此定义常量

override fun getConstants(): MutableMap<String, Any> =
    hashMapOf("DEFAULT_EVENT_NAME" to "New Event")

这样RN就可以通过getConstants获取到这些常量

console.log(CalendarModule.getConstants().DEFAULT_EVENT_NAME);

异步方法

Callbacks

对于想要知道异步方法的执行结果的情况,其中一个解决方法是使用callback回调,NativeModule提供一种特殊的参数,即Callback

package com.facebook.react.bridge;

/**
 * Interface that represent javascript callback function which can be passed to the native module as
 * a method parameter.
 */
public interface Callback {

  /**
   * Schedule javascript function execution represented by this {@link Callback} instance
   *
   * @param args arguments passed to javascript callback method via bridge
   */
  public void invoke(Object... args);
  
}

从源码可以看到callback只有一个invoke方法,并且接受Object数组。

Promises

另一种异步方法的处理则是通过为原生方法新增Promises参数

@ReactMethod
fun createCalendarEvent(name: String, location: String, promise: Promise) {
    try {
        val eventId = ...
        promise.resolve(eventId)
    } catch (e: Throwable) {
        promise.reject("Create Event Error", e)
    }
}

promise有resolvereject两个方法,前者代表成功执行后的回调,后者代表出现异常时的回调。这样定义的原生方法,在JS中有两种处理方式:

const onSubmit = async () => {
  try {
    const eventId = await CalendarModule.createCalendarEvent(
      'Party',
      'My House',
    );
    console.log(`Created a new event with id ${eventId}`);
  } catch (e) {
    console.error(e);
  }
};

第一种是通过await关键词他会阻塞直到函数执行resolve返回后。需要注意的是await关键词必须运行在async修饰的函数

const onSubmit = () => {
    CalendarModule.createCalendarEvent(
      'Party',
      'My House',
    ).then(eventId => {
        console.log(`Created a new event with id ${eventId}`);
    }).catch(e => {
        console.error(e);
    });
};

第二种是如上通过回调的处理方式

发送事件

原生模块可以向RN发送事件,RN可以注册监听想要的事件

...
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments
import com.facebook.react.modules.core.DeviceEventManagerModule
...

private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
    reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
      .emit(eventName, params)
}

...
val params = Arguments.createMap().apply {
    putString("eventProperty", "someValue")
}
...
sendEvent(reactContext, "EventReminder", params)

发送事件需要明确事件名和参数,并通过emit函数传入

import {DeviceEventEmitter} from 'react-native';
...
useEffect(() => {
    
    DeviceEventEmitter.addListener(
        eventName,
        notifyCallback,
    )

    return () => {
      DeviceEventEmitter.removeAllListeners(eventName);
    };
  }, []);

RN通过DeviceEventEmitter进行监听

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值