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中文網

相關文章
相關標籤/搜索