3.React Native在Android中本身定义Component和Module

React Native终于展现的UI全是Native的UI。将Native的信息封装成React方便的调用。javascript

那么Native是怎样封装成React调用的?Native和React是怎样交互的?php

ViewManager

UI组件:将Native的UI暴露出来供JS调用。css

  • Native组件封装成JS组件供JS调用。这里的一个问题是怎么将Native中的属性用在JS中。以及属性可以有哪些类型的?可以先思考一下。

如下Native的代码本身定义了一个View并定义了一个变化的属性color。html

public class MyCustomView extends View {

    private Paint mPaint;

    public MyCustomView(ReactContext context) {
        super(context);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(0xffff0000);
    }

    // 这里至关于可以改变color属性
    public void setColor(int color){
        mPaint.setColor(color);
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 測试代码。onMeasure中设置的值经过getWidth()/getHeight()拿到的不同,问题没找到
        setMeasuredDimension(300, 300);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }
}

建立一个ViewManager。java

react

public class MyCustomViewManager extends SimpleViewManager<MyCustomView> { protected static final String REACT_CLASS = "MyCustomView"; @Override public String getName() { // 返回了定义的View Module的名字 return REACT_CLASS; } @Override protected MyCustomView createViewInstance(ThemedReactContext reactContext) { return new MyCustomView(reactContext); // 建立一个View实例供JS使用。

} // 设置属性,必定需要加这个注解,否则不认识 @ReactProp(name = "color") public void setColor(MyCustomView view, String color) { view.setColor(Color.parseColor(color)); } }

建立一个ReactPackage,并在Application中使用。android

github

public class CustomReactPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<Class<?

extends JavaScriptModule>> createJSModules() { return Collections.emptyList(); } // 本身定义的ViewManager都可以加在这里。git

@Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Arrays.<ViewManager>asList( new MyCustomViewManager() ); } }

在Application中使用ReactPackage。express

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(), new CustomReactPackage() // 把本身定义的ReactPackage加进来
            );
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}

将Native组件封装成JS组件。

import React, { Component, PropTypes, } from 'react'; import { requireNativeComponent, View, UIManager, } from 'react-native'; const ReactNative = require('ReactNative'); // ReactNative经过import没用 export default class MyCustomView extends Component{ constructor(props){ super(props) } render(){ // {...this.props} 必定需要设置,不让你永远也看不到 return( <RCTMyCustomView {...this.props} </RCTMyCustomView>); } } MyCustomView.propTypes = { color: PropTypes.string, // 设置color属性 ...View.propTypes, // 这里必定需要设置,否则会报错。

has no propType for native prop。这个被坑了 }; var RCTMyCustomView = requireNativeComponent('MyCustomView', MyCustomView); // 拿到Native组件

而后就可以愉快的使用了。(最開始没有设置大小,仅仅是在Native的onMeasure中设置了大小,一直没有View出来,被坑了)

// 必定需要设置大小,否则永远看不到。 <MyCustomView color='#00ff00' style={{width:300, height:300}} />

假设是第一次使用封装UI Component的话。本身必定需要完整的尝试一遍。

  • 交互。

    Native将事件传递给JS、JS将事件传递给Native。想一下这样一个场景,点击了Native之后,JS怎么知道Native被点击了?以及JS是否能告诉Native需要干什么?固然需要了,并且React Native已经封装的很是好了。

在上面的MyCustomViewManager中实现一些方法就可以了。

getCommandsMap()和receiveCommand()用来处理JS向Native发送事件逻辑。getExportedCustomDirectEventTypeConstants()和addEventEmitters()相应了Native向JS发送事件逻辑。

private static final int CHANGE_COLOR = 1; /** * 可以接收的JS发过来的事件,返回来的数据是一组相应了方法名以及方法相应的一个ID(这个ID需要惟一区分)的Map。 * 这个在进入App的时候就会运行。获得相应的一组Map。 */ @Nullable @Override public Map<String, Integer> getCommandsMap() { return MapBuilder.of("changeColor", CHANGE_COLOR); } /** * 接收JS事件之后的处理。

JS会经过一些发送发送相应的指令过来,Native会由receiveCommand来处理。

* 事件过来时才会运行。

*/ @Override public void receiveCommand(MyCustomView root, int commandId, @Nullable ReadableArray args) { switch (commandId) { case CHANGE_COLOR: root.changeColor(); break; } } /** * 暴露了在JS中定义的方法,好比如下的"onChangeColor"是定义在JS中的方法。 * 这个在进入App的时候就会运行 * * Returned map should be of the form: * { * "onTwirl": { * "registrationName": "onTwirl" * } * } */ @Nullable @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("changeColor", MapBuilder.of("registrationName", "onChangeColor")) .build(); } /** * 发射入口,至关于将Native的一些事件也注冊给JS。

* * 这个在进入App的时候就会运行。

*/ @Override protected void addEventEmitters(final ThemedReactContext reactContext, final MyCustomView view) { super.addEventEmitters(reactContext, view); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 调用了JS相应的方法。 reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() .dispatchEvent(new ClickEvent(view.getId())); } }); }

在上面的代码中可以看到Native会接受一个1(CHANGE_COLOR)的指令以及会回调一个onChangeColor的方法到JS。那么现在就在JS中实现。把完整的JS代码贴了一遍。凝视也写在了里面。

const ReactNative = require('ReactNative');

const CUSTOM_VIEW = "custom_view";

export default class MyCustomView extends Component{
  constructor(props){
    super(props)

    this._onChange = this._onChange.bind(this); // 必定需要这样调用才会把属性绑定过来
  }

  // 把事件给Native
  _changeColor() {  // is not a function?没有设置this._onChange = this._onChange.bind(this);的时候

    let self = this;
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(self.refs[CUSTOM_VIEW]),
      1,  // 发送的commandId为1
      null
    );
  }

  _onChange() {
    if (!this.props.handleClick) {
      return;
    }
    this.props.handleClick();
  }

  render(){
    // 设置ref,没弄明确为何必定需要设置ref,大概是_changeColor中的findNodeHandle需要
    return(
      <RCTMyCustomView 
        ref={CUSTOM_VIEW}
        {...this.props}
        onChangeColor={() => this._onChange()}>
      </RCTMyCustomView>);
  }
}

MyCustomView.propTypes = {
  handleClick: PropTypes.func,
  color: PropTypes.string,  // 设置一个属性
  ...View.propTypes,
};

var RCTMyCustomView = requireNativeComponent('MyCustomView', MyCustomView, {
  nativeOnly: {onChangeColor: true}
});

注意上面用到了nativeOnly。有时候有一些特殊的属性,想从原生组件中导出,但是又不但愿它们成为相应React封装组件的属性。举个样例,Switch组件可能在原生组件上有一个onChange事件,而后在封装类中导出onValueChange回调属性。这个属性在调用的时候会带上Switch的状态做为參数之中的一个。这种话你可能不但愿原生专用的属性出现在API之中。也就不但愿把它放到propTypes里。但是假设你不放的话,又会出现一个报错。

解决方式就是带上nativeOnly选项。

(来自 http://reactnative.cn/docs/0.41/native-component-android.html#content)

现在就可以愉快的调用了。

<MyCustomView
  ref='view'
  color='#00ff00'
  handleSizeClick={() => this._handleSizeClick()}
  handleClick={() => this._handleClick()}
  style={{width:300, height:300}} />

建议刚開始学习的人好好的实践一遍。



最后的结果

NativeModule

Native模块:定义Native的模块供JS调用。

这种场景会比較的多,比方Toast,在JS中没有Toast这类东西。但是Android/IOS中却非常常见。

  • JS调用Native组件

封装一个moudle供JS调用。

注意里面凝视。

@ReactModule(name = "DemoToast")
public class DemoToastModule extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

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

    // Module的名称
    @Override
    public String getName() {
        return "DemoToast";
    }


    /** * 这里定义的值可以被JS中引用。JS引用的时候.SHORT就会相应到相应的Toast.LENGTH_SHORT */
    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
    }

    /** * 经过Callback回调到JS */
    @ReactMethod
    public void show(String message, int duration, Callback callback) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
        callback.invoke("Egos");
    }
}

JS将Native module转化成JS组件。

import { NativeModules } from 'react-native'; RCTDemoToast = NativeModules.DemoToast; // 获取到Native Module var DemoToast = { /** * 认为这里不是很是好理解,但是这里相应的那个值(SHORT或者LONG)确实 * 是相应了上面Java代码中的getConstants相应的信息。

*/ SHORT: RCTDemoToast.SHORT, LONG: RCTDemoToast.LONG, show(message, duration){ RCTDemoToast.show(message, duration, (msg) => { var str = msg; }); } }; module.exports = DemoToast;
  • 交互。Native回调信息给JS。

@ReactMethod public void show(String message, int duration, Callback callback) { Toast.makeText(getReactApplicationContext(), message, duration).show(); callback.invoke("Egos"); // callback回调信息。

注意上面的RCTDemoToast.show方法第三个參数。 }

在JS中注冊testMethod。Native直接调用JS。

componentWillMount() {
  DeviceEventEmitter.addListener('testMethod', (event) => {var s = event;} ); }

如下Native代码发送指令就会运行上面代码。

WritableMap params = Arguments.createMap(); params.putString("xixi","Egos"); sendEvent(getReactApplicationContext(), "testMethod", params); /** * 也可以直接发送事件给JS代码 */ private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); // 会回调到上面注冊的testMethod。

}

JavaScriptModule

JS模块:com.facebook.react.CoreModulesPackage中有展现出来一些信息,AppRegistry、RCTEventEmitter(相应了RCTNativeAppEventEmitter)等等。



源代码中定义的JS Module

至关于运行JS代码的时候会相应去运行Native相应的代码。这部分不是View,不是Native Module。 这部份内容还不是很是理解,没有找到合适的样例。兴许有问题补充。

思考

  • 查看ReactPackage.java这个类,里面的信息说明了UI组件、Native模块、JS模块这三个信息。也就是咱们寻常定义的这三种信息都需要在这里相应的注冊。
public interface ReactPackage {

  /** * @return list of native modules to register with the newly created catalyst instance */
  List<NativeModule> createNativeModules(ReactApplicationContext reactContext);

  /** * @return list of JS modules to register with the newly created catalyst instance. * * IMPORTANT: Note that only modules that needs to be accessible from the native code should be * listed here. Also listing a native module here doesn't imply that the JS implementation of it * will be automatically included in the JS bundle. */
  List<Class<? extends JavaScriptModule>> createJSModules();

  /** * @return a list of view managers that should be registered with {@link UIManagerModule} */
  List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
  • React Native将很是多的Native的UI以及组件都封装成了JS供JS调用,对外暴露的接口应该会愈来愈全面以及愈来愈简单,期待将来的发展。
  • 近期用React Native写了一点代码,原本准备写写一些控件的使用以及一些坑,但是想一想仍是算了。每一个控件使用在不一样的地方可能就有一些不同的问题,这些还得花时间慢慢解决。

參考

Native Modules

Native UI Components

React Native中文网

相关文章
相关标签/搜索