兩篇文章搞定ReactNative之搞定ReactNative

View

View組件是ReactNative的最基礎(chǔ)組件,所有ReactNative UI都需要在View的基礎(chǔ)上開發(fā)。View組件支持Flexbox布局、樣式以及觸摸事件等,在不同平臺上會被解釋程不同的Native元素,如Android上的View,IOS上的UIView

<View style={{flex:1}}>
    <View></View>
</View>

StyleSheet

StyleSheet是一種類似CSS樣式表的抽象

const styles = StyleSheet.creat({
    body:{
        backgroundColor:"#f00",
        color:"#fff"
    },
    title:{
        color:"#abc",
        fontSize:20
    }
});

...
<View style={styles.body}>
    ...
</View>

內(nèi)聯(lián)樣式

在組件里面定義樣式

<View style={{width:100,height:20,backgroundColor:"#f00"}}></View>

外聯(lián)樣式

在組件內(nèi)調(diào)用外部定義的樣式

<View style={styles.body}></View>

組合樣式

在組件內(nèi)調(diào)用多個外部樣式、定義多個內(nèi)聯(lián)樣式或是組合外聯(lián)樣式及內(nèi)聯(lián)樣式

<View style={[styles.body,{backgroundColor:"#0f0",padding:4},styles.title]}></View>

1.組合樣式有優(yōu)先級,從左到右優(yōu)先級依次升高
2.如定義相同屬性的樣式,優(yōu)先級高的覆蓋優(yōu)先級低的

ReactNative Appclication

Application 是Android App的根實例,同一進(jìn)程(一個App多個進(jìn)程的時候會產(chǎn)生多個Application)內(nèi)所有頁面共享一個Application(Context)。Application中同時可以保存一些全局的數(shù)據(jù)在不同頁面實例之間共享。ReactNative App需要在Application中配置其橋接參數(shù),最后傳給底層的解釋引擎

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 MyMapPackage()
      );
    }

    @Override
    protected String getJSMainModuleName() {
      return "index";
    }
  };

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

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

}

  • getPackages方法中需要導(dǎo)出ReactNative的橋接封裝以及開發(fā)者自己的橋接封裝,后面的小節(jié)將逐一說明這些自定義的橋接。
  • getJSMainModuleName方法講告訴解釋引擎在JS中App的入口在哪里

調(diào)用原生Api(以Android為例)

在JS中獲取Native信息

在JS中獲取Native信息,需要NativeModules實現(xiàn),需要在JS及Native兩端分別構(gòu)建代碼,然后通過NativeModules做中間橋梁來做通信
以獲取App版本號為例
Android端
1.實現(xiàn)ReactContextBaseJavaModule

public class RNDiyModule extends ReactContextBaseJavaModule {
    private static final String NAME = "RNDiyModule";
    ReactApplicationContext reactContext;

    public RNDiyModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }

    @Override
    public String getName() {
        return NAME;
    }

    @ReactMethod
    public void getChannelid(Callback callback) {
        String channelid = ((MainApplication) reactContext.getApplicationContext()).getChannelId();
        callback.invoke(channelid);
    }
}
  • getNameReactContextBaseJavaModule內(nèi)建的方法,特別重要,在JS里面,我們會通過這個返回值來操作這個類里面的方法比如NativeModules.RNDiyModule.getChannelid()
  • getChannelid是我們需要被JS調(diào)用的方法名,值得注意的是JS調(diào)用Native方法全部是異步的,因此需要用Callback(Promise)的方法將值異步回調(diào)給JS

2.實現(xiàn)ReactPackage

public class RNDiyPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new RNDiyModule(reactContext));
        return modules;
    }

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

    }

}

  • ReactPackage將是Native與JS通信的重要橋梁,原生方法調(diào)用,原生UI調(diào)用都必須在這兒注冊
  • 將RNDiyModule注冊到createNativeModules的列表中

3.將ReactPackage注冊到ApplicationgetPackages

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),new RNDiyPackage()
    );
}

JS中

NativeModules.RNDiyModule.getChannelid((channelid) => {
    console.log("RNDiyModule.getChannelid", channelid);
})

Native中主動向JS發(fā)送數(shù)據(jù)

Navtive中主動想JS發(fā)送數(shù)據(jù)需要使用DeviceEventEmitter
Android端

public void sendChannelId(String channelid) {
    this.channelid = channelid;
    WritableMap params = Arguments.createMap();
    params.putString("channelid", channelid);
    sendEvent(getReactNativeHost().getReactInstanceManager().getCurrentReactContext(), "onChannelIdResult", params);
}

private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
    reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
}
}

需要注意的是,在使用getJSModule的時候一定要注意上下文是ReactContext
emit方法參數(shù)中傳了eventName(onChannelIdResult),這個就是在JS中接受的事件名
emit接受的參數(shù)類型是WritableMap

JS中接受數(shù)據(jù)

DeviceEventEmitter.
    addListener('onChannelIdResult', data => console.log(data.channelid););

調(diào)用Native UI(Android)

創(chuàng)建Native UI

Native UI創(chuàng)建應(yīng)當(dāng)遵循Native 平臺的規(guī)則來進(jìn)行,
下面以一個標(biāo)題欄為例子
1.創(chuàng)建布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="GOOD" />

    <ImageView
        android:id="@+id/goback"
        android:layout_width="40dp"
        android:layout_height="match_parent"
        android:src="@drawable/mapkit_ic_back" />
</RelativeLayout>

2.創(chuàng)建Native UI

public class TitleBar extends LinearLayout {
    private TextView title;
    private ImageView goback;

    public TitleBar(Context context) {
        this(context, null);

    }

    public TitleBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.v_title_lay, this, true);
        title = (TextView) findViewById(R.id.title);
        goback = (ImageView) findViewById(R.id.goback);
        goback.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });
    }
    public void setTitle(String titleStr) {
        if (title != null) title.setText(titleStr);
    }

    public void setTitleColor(String color) {
        if (title != null) title.setTextColor(Color.parseColor(color));
    }
}

將Native UI注冊到ViewManager

Native UI創(chuàng)建完后只能在Native中使用,要想在JS中調(diào)用,還需要將Native UI注冊到連接橋上,ViewManager就是這個連接橋

public class MyTitleBarViewManager extends SimpleViewManager<TitleBar> {
    private static final String NAME = "RCTTitleBar";

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    protected TitleBar createViewInstance(ThemedReactContext reactContext) {
        return new TitleBar(reactContext);
    }

}

前面在講JS調(diào)用Native方法的時候講到 NativeModule要綁定到ReactPackage中,ViewManager也同樣需要綁定到ReactPackage中,不過需要綁定的是createViewManagers方法里

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

JS中調(diào)用Native UI

前面幾步已經(jīng)在Native中將Native UI的工作做好了,在這兒需要的是在JS中將Native UI導(dǎo)出來,供JS調(diào)用。
這兒需要用到一個方法requireNativeComponent,這個方法可以將我們定義的Native UI找出來,第一個參數(shù)就是我們在ViewManager中定義的NAME

module.exports = requireNativeComponent('RCTTitleBar');

這樣在可以在JS中調(diào)用RCTTitleBar
ReactJS目前遵循ES6的語法,因此我們需要包裝一下,讓他更好的使用

const RCTTitleBar = requireNativeComponent('RCTTitleBar');
export default class TitleBar extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <RCTTitleBar
                {...this.props}
            />
        )
    }
}

如此一來就可以在JS的任意界面調(diào)用TitleBar組件了

導(dǎo)出Native UI屬性給JS

Native UI在很多的時候會提供一些seter方法供系統(tǒng)改變其屬性,比如setColor改變顏色,setName改變名稱。這些單一的方法我們在JS中調(diào)用不是很方便,ReactNative的ViewManager中給了我們導(dǎo)出Native UI Props給JS用的方法

@ReactProp
@ReactPropGroup

我們將TitleBar的setTitleColorsetTitle分別導(dǎo)出為colortitle


@ReactProp(name = "color")
public void setColor(TitleBar view, String color) {
    view.setTitleColor(color);
}

@ReactProp(name = "title")
public void setTitle(TitleBar view, String title) {
    view.setTitle(title);
}

JS中使用Native UI屬性

前面在JS中調(diào)用Native UI的時候用到了requireNativeComponent,我們只給了ViewManager中定義的Name,并沒有進(jìn)行Props限定,現(xiàn)在我們加上PropTypes限定

let iface = {
    name: 'TitleBar',
    propTypes: {
        color: PropTypes.string,
        title: PropTypes.string,
        ...ViewPropTypes,
    },
};

const RCTTitleBar = requireNativeComponent('RCTTitleBar', iface);

怎么調(diào)用呢?

<TitleBar
    style={{flex: 1, height: 40}}
    title={"武漢天空"}
    color={"#198198"}
/>

Native UI在使用的時候一定要注意設(shè)定height,要不會出現(xiàn)height==0,看不見UI的問題

Native UI中發(fā)生數(shù)據(jù)或事件給JS

在前面講到Native向JS發(fā)送事件的使用用到了DeviceEventEmitter,在這兒我將不再使用這個而是用EventDispatcher
1.首先我們要定義一個Event對象來承載我們的事件
事件需要繼承Event來實現(xiàn),下面我們定義一個按返回按鈕出發(fā)的返回事件

public class TopGobackEvent extends Event<TopGobackEvent> {
    public static final String EVENT_NAME = "topGoback";

    public TopGobackEvent(int viewTag) {
        super(viewTag);
    }

    @Override
    public String getEventName() {
        return EVENT_NAME;
    }

    @Override
    public void dispatch(RCTEventEmitter rctEventEmitter) {
        WritableMap data = Arguments.createMap();
        rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
    }
}

自定義Native UI也好,頁面也好,都需要一個NAME,同樣Event也需要一個EventName,topGoback就是我們定的名稱,后面還要將他進(jìn)行重新定向
2.用EventDispatcher發(fā)送事件
EventDispatcher就是事件分發(fā)的意思,所有事件都需要通過他分發(fā)到JS中,我跟給他pack一下

protected static void dispatchEvent(TitleBar titleBar, Event event) {
    ReactContext reactContext = (ReactContext) titleBar.getContext();
    EventDispatcher eventDispatcher =
            reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
    eventDispatcher.dispatchEvent(event);
}

我們只要在發(fā)生事件的時候調(diào)用dispatchEvent將事件給他就好了,接下來我們在TitleBar的goback點擊事件中分發(fā)這個TopGobackEvent

goback.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        onGoback();
    }
});
public void onGoback() {
    dispatchEvent(this, new TopGobackEvent(this.getId()));
}

這樣子在goback被點擊后將會觸發(fā)TopGobackEvent事件分發(fā),做完上面這些運行程序,你會發(fā)現(xiàn)出現(xiàn)錯誤了,為什么呢,因為還需要在ViewManager中給事件重命名,或說定義個在JS中能接收的鉤子。
3.定義JS中接收事件的方法名

@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.<String, Object>builder()
            .put(TopGobackEvent.EVENT_NAME, MapBuilder.of("registrationName", "onGoback"))
            .build();
}

在ViewManager中有若干個* getExported**的方法,有冒泡的,有直達(dá)的,涉及到了ReactNative事件方面的東西。這兒我們用了直達(dá)事件,將TopGobackEvent事件綁定到了onGoback

JS中接受Native UI事件

上一小節(jié)所有的工作就是為了能在Native UI中發(fā)生事件后傳到JS。一個巴掌拍不響,我們在這兒做另一個巴掌。上一節(jié)最后我們將TopGobackEvent事件綁定到了onGoback上,onGoback就是我們在這兒接受的鉤子,他是個function類型的Prop
在JSrequireNativeComponent方法中Props類型限定的地方加上onGoback: PropTypes.func
怎么使用呢?按屬性使用

<RCTTitleBar
    {...this.props}
    onGoback={e => {}}
/>

JS中發(fā)生數(shù)據(jù)或調(diào)用Native UI方法

來而不往非禮也,Native UI咬了JS一口,要不要也還牙呢?看情況
前面我們通過Prop的方法可以調(diào)用一些Native UI的seter方法,若是其他的呢,難道就沒有辦法了?比如有返回值的方法?答案是肯定的,我們來做以個簡單,簡單到什么程度呢,我們主動調(diào)用TitleBar改變背景色的方法
1.在TitleBar中增加更改背景色的方法

public void updateBarColor(String color) {
    setBackgroundColor(Color.parseColor(color));
}

2.咬Native UI一口
首先請出我們的必殺器UIManager.dispatchViewManagerCommand

 updateBarColor(barColoor) {
    UIManager.dispatchViewManagerCommand(
        findNodeHandle(this),
        UIManager.RCTTitleBar.Commands.setBarColor,
        [barColoor]
    );
}

dispatchViewManagerCommand第一個參數(shù)是RCTTitleBar的引用,第二個command,這個需要在VIewManager中定義好,第三個參數(shù)是個數(shù)組,也就是給Native UI中command指定的方法的參數(shù)

如上可以看到,在JS中咬Native一口也是不容易的,需要授權(quán)才行啊

Native UI中接受JS事件調(diào)用

前面說JS中調(diào)用Native UI中的方法需要授權(quán)?,F(xiàn)在我們來做指令處理
1.首先用到的是聲明一個指令,這個指令是int類型的

private static final int CMD_UPDATE_BAR_COLOR = 1;

2.綁定這個指令到一個鉤子上
綁定指令到鉤子上需要用到ViewManager中的一個方法getCommandsMap

@Override
public @Nullable
Map<String, Integer> getCommandsMap() {
    return MapBuilder.of(
            "setBarColor", CMD_UPDATE_BAR_COLOR
    );
}

3.接受指令,并執(zhí)行相應(yīng)方法receiveCommand

@Override
    public void receiveCommand(TitleBar root, int commandId, @Nullable ReadableArray args) {
        super.receiveCommand(root, commandId, args);
        switch (commandId) {
            case CMD_UPDATE_BAR_COLOR: {
                String color = args.getString(0);
                root.updateBarColor(color);
            }
            break;
        }
    }

如此,JS調(diào)用Native UI的流程也就通了,這個例子只是沒有返回值的調(diào)用,有返回值的怎么做呢?前面講JS調(diào)用Native信息的時候已經(jīng)說了,JS調(diào)用Native是異步的,需要用callback或是Promise來做。

獲取組件寬高

很多時候由于內(nèi)容的不確定性,導(dǎo)致我們沒有辦法將組件的Size固定下來,因此在一些需要計算寬高的情況下需要知道組件的Size,ReactNative提供了onLayout回調(diào)來處理此事(無論IOS還是Android組件的初始Size都是(0,0),因此需要渲染完畢才能得到大?。?/p>

<View
    onLayout={(evt) => {
         NativeModules.UIManager.measure(evt.target, (x, y, width, height, pageX, pageY) => {
      });
    }}
>
</View>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容