React Native終於展現的UI全是Native的UI。將Native的信息封裝成React方便的調用。javascript
那麼Native是怎樣封裝成React調用的?Native和React是怎樣交互的?php
UI組件:將Native的UI暴露出來供JS調用。css
如下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}} />
建議剛開始學習的人好好的實踐一遍。
Native模塊:定義Native的模塊供JS調用。
這種場景會比較的多,比方Toast,在JS中沒有Toast這類東西。但是Android/IOS中卻非常常見。
封裝一個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;
@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。}
JS模塊:com.facebook.react.CoreModulesPackage中有展現出來一些信息,AppRegistry、RCTEventEmitter(相應了RCTNativeAppEventEmitter)等等。
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);
}