React-native原生交互之ReactPackage注册

本篇文章主要是简单介绍下 ReactPackage 注册方法中的三个方法的作用和使用。

在0.47.2版本之前,Android原生类如果实现ReactPackage,需要实现
createNativeModules 、 createViewManagers、 createJSModules三个方法,在此之后只需要实现createNativeModules 、 createViewManagers 两个方法。

那么这三个方法的主要作用是什么呢?

createNativeModules 创建原生module,提供原生方法供react native调用
createViewManagers 创建原生View,提供原生View供react native调用
createJSModules 创建JS module, 提供react native方法供原生端调用

下面我们介绍一下这三个方法的具体使用:


createNativeModules

原生Module,注册原生(Android)中继承ReactContextBaseJavaModule的类
方法上添加了@ReactMethod注解的,可以供JS端使用
Android代码 CommonUtilPackage 类

public class CommonUtilPackage  implements ReactPackage {

    @Override
    public List createNativeModules(ReactApplicationContext reactContext) {
        List modules = new ArrayList<>();
        modules.add(new CommonUtilModule(reactContext));
        return modules;
    }

public List> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

}

Android代码 CommonUtilModule 类

public class CommonUtilModule extends ReactContextBaseJavaModule{

    public CommonUtilModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "CommonUtilModule";
    }

   // 简单的原生吐司方法
   @ReactMethod
   public void toast(String content){
     Toast.makeText(mContext, content ,1).show();
   }

}

JS代码

  import React from 'react';
  import { NativeModules, View,  Text } from 'react-native';

 export default class App.js extends React.Component{

  componentDidMount(){
    // 此处未做平台判断只是举例使用
    NativeModules.CommonUtilModule.toast('调用原生吐司方法'); 
  }
  
  render(){
    return (
       WelCome to React Native.
    );
  }
}

相同的方式,我们看一下react-native包中的Alert是如何实现的

通常我们使用的话

import { Alert } from 'react-native';

  // 在某个方法中使用下面这段代码并调用
  //  iOS和Android上都可用
Alert.alert(
  'Alert Title',
  'My Alert Msg',
  [
    {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')},
    {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
    {text: 'OK', onPress: () => console.log('OK Pressed')},
  ],
  { cancelable: false }
)

看RN官网对Alert的定义为(以Android为例)

在 Android 上最多能指定三个按钮,这三个按钮分别具有“中间态”、“消极态”和“积极态”的概念:

如果你只指定一个按钮,则它具有“积极态”的属性(比如“确定”);两个按钮,则分别是“消极态”和“积极态”(比如“取消”和“确定”);三个按钮则意味着“中间态”、“消极态”和“积极态”(比如“稍候再说”,“取消”,“确定”)。

在 Android 上默认情况下点击提示框的外面会自动取消提示框。你可以提供一个额外参数来处理这一事件:{ onDismiss: () => {} }。

还有另外一个参数也可以用来阻止提示框被自动取消,即{ cancelable: false }

API

alert()

static alert(title, message?, buttons?, options?)

react-native包中的实现(以0.48.4版本为例)
实际上,我们的

import { Alert } from 'react-native';

引入的模块为

node_modules/react-native/Libraries/Alert/Alert.js的文件中的Alert类

// 该类中的静态方法
static alert(
    title: ?string,
    message?: ?string,
    buttons?: Buttons,
    options?: Options,
    type?: AlertType,
  ): void {
    if (Platform.OS === 'ios') {
      if (typeof type !== 'undefined') {
        console.warn('Alert.alert() with a 5th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.');
        AlertIOS.alert(title, message, buttons, type);
        return;
      }
      AlertIOS.alert(title, message, buttons);
    } else if (Platform.OS === 'android') {
      AlertAndroid.alert(title, message, buttons, options);
    }
  }

以Android为例,实际上该方法调用的代码为

AlertAndroid.alert(title, message, buttons, options);

该文件中还有一个AlertAndroid类,存在同样的静态方法,最后调用了

NativeModules.DialogManagerAndroid.showAlert();
根据我们前端调用原生模块的方法,我们找到module名为DialogManagerAndroid的类

路径为:
node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/
dialog/DialogModule.java

再查看showAlert方法,实际上是Android原生(同包下)AlertFragment的show方法去实现该效果的。


createViewManagers

原生模块中提供给JS端使用的自定义View,例如目前RN提供的View、Text、ScrollView etc.

核心掌握: 原生界面 、 属性、CSS样式布局、 事件、 方法

原生界面
1、 创建一个类继承SimpleViewManager,实现父类的方法

  @Override
  public String getName() {
    return "ViewName";
  }
   
  @Override
  public View createViewInstance(ThemedReactContext context) {
    return new View(context);
  }

这里我们创建的类中的createViewInstance方法返回的View,则是我们后面将在RN里面使用的View

2、 注册我们的原生View

在我们实现了Reactpackage的类中的 createViewManagers方法中,注册我们的View

@Override
    public List createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.asList(new MyViewManager());
    }

3、 和我们之前的native module一样,需要注册我们的package

  @Override
    protected List getPackages() {

      return Arrays.asList(
        new MyPackage()
      );
    }

4、 在JS中导出原生组件

// 属性类型
const ViewNameProperty = {
  name: 'CloudPlayerView',
  propTypes: {
    ...View.propTypes, //包含默认的View的属性,如果没有这句会报‘has no propType for native prop’错误
  },
};

// 导出原生View  原生View名称 、 属性类型
const ViewName = requireNativeComponent('ViewName', ViewNameProperty);

完成以上四步,就能在RN中使用我们Android创建的原生组件了

    
         { this._assignRoot = ref; }}
        />
     

属性
在我们继承SimpleViewManager的类中,添加属性方法
1、 添加属性

  // name里面的值为 我们对应的RN中的属性名称
  @ReactProp(name="title")
    public void setTitle(View view,@Nullable String title){
        // doSomething
        view.setTitle(title);
    }

2、 JS中在我们导出组件的 属性里面添加该属性

  const ViewNameProperty = {
  name: 'ViewName',
  propTypes: {
   titile: PropTypes.string, // 对应的原生属性
    ...View.propTypes, //包含默认的View的属性,如果没有这句会报‘has no propType for native prop’错误
  },
};

// 导出原生View  原生View名称 、 属性类型
const ViewName = requireNativeComponent('ViewName', ViewNameProperty);

3、 使用

  

以上三步就完成了一个属性的添加了,可以根据自己的实际事情去添加自己View的属性。

CSS样式布局

研究中,嘻嘻~

事件

1、 注册事件

  // 在ViewManager文件中实现该方法
// 注册RN事件 eventName 是事件名称
    @Override
    public @Nullable Map getExportedCustomDirectEventTypeConstants() {
        MapBuilder.Builder builder = MapBuilder.builder();
        builder.put(nativeEventName, MapBuilder.of("registrationName", jsEventName));
        return builder.build();
    }

2、 RN中增加View的事件属性

  // 属性类型
const ViewName = {
  name: 'ViewName',
  propTypes: {
    jsEventName: Function, // 增加的事件
    ...View.propTypes, //包含默认的View的属性,如果没有这句会报‘has no propType for native prop’错误
  },
};

3、 RN中添加事件的方法

       

4、 原生分发事件

    /**
     * eventEmitter为创建的RCTEventEmitter对象                 reactContext.getJSModule(RCTEventEmitter.class)
      viewId: 对应的原生View的父类方法的值
      nativeEventName: 原生分发的事件名称
    */
    eventEmitter.receiveEvent(viewId, type, nativeEventName);

常量

1、 在ViewManager类中实现父类的getExportedViewConstants方法

//  待实践  嘻嘻~ 
@Override
  public @Nullable Map getExportedViewConstants() {
    return MapBuilder.of(
        "AutoCapitalizationType",
        MapBuilder.of(
            "none",
            0,
            "characters",
            InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
            "words",
            InputType.TYPE_TEXT_FLAG_CAP_WORDS,
            "sentences",
            InputType.TYPE_TEXT_FLAG_CAP_SENTENCES));
  }

方法

原生UI里面,会存在JS端调用原生UI中的方法的问题;前面我们讲过了事件的用法,事件是通过回调方式,原生端回调传值调用JS的方法,那么方法则是和事件相反,是通过JS端传值主动调用原生端的方法。

下面我们来看下方法是如何使用的:

  1. 原生端将方法名和方法的id注册一下
   // 在我们的ViewManager的类中
   //  注册供RN调用的原生方法
   @Nullable
   @Override
   public Map getCommandsMap() {
       return MapBuilder.of("native_method_name",1); 
   }
  1. 原生端写好JS端调用该方法时在原生端的映射
  // 接收RN调用的方法
    @Override
    public void receiveCommand(View root, int commandId, @Nullable ReadableArray args) { // args为我们在js端传入的参数
        switch (commandId){
            case 1: // 这里的1和我们之前注册的id对应
                // doSomeThing
                Log.d("===","RN调用原生UI的方法");
                break;
                default:
                    break;
        }
    }
  1. JS端调用我们在原生端注册的方法
import { UIManager } from 'react-native';

// 在js端定义的方法
  native_method_name = () => {
  // 1、ViewName为你在原生创建的ViewManager类的getName()方法返回的值 
  // 2、 这里传入的null为js端传给原生端的参数
    UIManager.dispatchViewManagerCommand(
      findNodeHandle(this._assignRoot), UIManager. ViewName.Commands.native_method_name, null);
 } 

完成以上三步,就可以在原生UI模块里面,js端主动调用原生端的方法了~


createJSModules

JS模块中提供方法给原生端调用的,过程相对繁琐

Android 代码 新增一个 Native 接口,继承 JavaScriptModule 接口


public interface AppModuleInitializer extends JavaScriptModule {
    void init(String name);
} 

Android 代码 注册该接口

  public class AppReactPackage extends MainReactPackage {

    @Override
    public List> createJSModules() {
        List> javaScriptModules = new ArrayList<>();
        javaScriptModules.addAll(super.createJSModules());
        javaScriptModules.add(AppModuleInitializer.class);
        return javaScriptModules;
    }
}


JS模块 创建同名的AppModuleInitializer.js 的文件

class AppModuleInitializer {
    init(json: string) {
        // do something
    }
}

AppModuleInitializer = new AppModuleInitializer();

// 注册
BatchedBridge.registerCallableModule(
    'AppModuleInitializer',
    AppModuleInitializer);

module.exports = AppModuleInitializer;

原生使用方式


  AppModuleInitializer initModule = context.getJSModule(AppModuleInitializer.class);
  initModule.init("调用JS端代码");

目前该方法在react-native0.47.2中已经被废弃了


有问题可留言,欢迎各大Coder 讨论、指正问题~

你可能感兴趣的:(React-native原生交互之ReactPackage注册)