在現階段我們的RN實踐都是基於發行過的APP,譬如將從某個入口進入的子模組都替換成RN頁面。那麼我們可以將這個子模組設計成一個通用RN容器,所有的RN頁面都在RN容器內部跳轉。
RN容器在iOS使用UIViewController、Android使用Activity或者Fragment,載入bundle檔案,正常情況下,一個模組只有一個bundle檔案。
要實現頁面的跳轉,我們可以使用Navigator組件,具體使用可以參考:http://blog.csdn.net/codetomylaw/article/details/52059493
還有幾個問題需要解決:
1、導覽列
在原生App中導覽列通常是統一管理的,那麼在通用容器中,我們可以定義一個通用的RN導航。
2、Native跳轉RN容器
使用正常的Native跳轉方式即可,譬如在Android中 startActivity 。
3、RN容器返回Native介面
由於導覽列已經是RN介面編寫的,那麼Native端就需要提供一個橋接的方法給RN調用,橋接方法需要實現的邏輯:finish掉初始化的RN容器
4、處理安卓系統返回鍵
詳細見Demo代碼
好,我們通過一個簡單的Demo來示範。
我們實現的效果是
1、從Native頁面跳轉RN頁面A,RN頁面A是由RN容器載入,點擊左上方可以返回到Native介面
2、點擊RN頁面A中的“跳轉詳情”可以跳轉到RN頁面B
3、點擊RN頁面B中的左上方或安卓物理返回鍵,可以返回到RN頁面A
4、點擊RN頁面B中的左上方或安卓物理返回鍵,可以阻斷頁面的返回,實現我們自己的邏輯
5、點擊RN頁面B中的分享,可以調用回調
頁面A如下圖:
頁面B如下圖:
導航組件代碼如下:Nav.js
import React, { Component } from 'react';import { StyleSheet, View, Text, Image, TouchableOpacity, Platform, NativeModules,} from 'react-native';const {CommonDispatcher} = NativeModules;//通用瀏覽組件export default class Nav extends Component { constructor(props) { super(props); height = (Platform.OS === 'ios') ? 64 : 45; leftWidth = 60; rightWidth = 60; } //控制返回事件,navigator返回 或 返回到原生頁面 back() { const { navigator } = this.props; if(navigator) { const routers = navigator.getCurrentRoutes(); if (routers.length >1) { navigator.pop(); }else{ //此處為橋接,需要finish 掉RN殼,跳轉到原生頁面 CommonDispatcher.toBack({}); } } } //左上方事件 leftCallBack() { if (this.props.leftCallBack) { this.props.leftCallBack(); }else { this.back(); } } //右上方事件 rightCallBack(){ if (this.props.rightCallBack) { this.props.rightCallBack(); } } render() { //左邊返回圖片可隱藏 let leftView = this.props.hiddenBack ? <View style={styles.leftView} /> :( <TouchableOpacity onPress={this.leftCallBack.bind(this)}> <View style={styles.leftView}> <Image source={{uri:"nav_back"}} style={styles.image}/> </View> </TouchableOpacity>); //標題現在只支援文本,樣式後續也可支援修改 let centerView = <Text style={styles.title}>{this.props.title}</Text>; //右上方地區目前只支援文本,後續可支援圖片或圖文 let rightView = ( <TouchableOpacity onPress={this.rightCallBack.bind(this)}> <Text style={styles.rightTitle}>{this.props.rightTitle}</Text> </TouchableOpacity>); return ( <View style={styles.container} height={height} backgroundColor={this.props.backgroundColor}> <View style={styles.leftView} width={leftWidth} >{leftView}</View> <View style={styles.centerView} >{centerView}</View> <View style={styles.rightView} width={rightWidth} >{rightView}</View> </View> ); }}const styles = StyleSheet.create({ container: { justifyContent:'space-between', flexDirection:'row', alignItems:'center', paddingTop:(Platform.OS === 'ios') ? 20 : 0, }, leftView:{ flexDirection:'row', alignItems:'center', }, rightView:{ flexDirection:'row', alignItems:'center', justifyContent:'flex-end', }, centerView:{ flex:1, flexDirection:'row', alignItems:'center', justifyContent:'center', }, image: { marginLeft:20, width:15, height:15, }, title: { fontSize:17, color:'#ffffff', }, rightTitle: { marginRight:15, color:'white' },});
容器組件代碼如下(HomePage也就是RN頁面A):page.js
'use strict';import React, { Component } from 'react';import { Platform, Navigator, BackAndroid, NativeModules, View, Text, AppRegistry, TouchableOpacity,} from 'react-native';import Nav from './Nav.js';import DetailPage from './DetailPage';const {CommonDispatcher} = NativeModules;export default class PageIndex extends Component { constructor(props) { super(props); } componentWillMount() { if (Platform.OS === 'android') { //監聽安卓物理按鍵返回 BackAndroid.addEventListener('hardwareBackPress', this.onBackAndroid); } } componentWillUnmount() { if (Platform.OS === 'android') { BackAndroid.removeEventListener('hardwareBackPress', this.onBackAndroid); } } //處理安卓物理back鍵 onBackAndroid = () => { let nav = this.navigator; let routers = nav.getCurrentRoutes(); // 當前頁面不為root頁面時的處理 if (routers.length >1) { let top = routers[routers.length - 1]; let handleBack = top.handleBack; if (handleBack) { // 路由或組件上決定這個介面自行處理back鍵 handleBack(); return true; } // 預設處理 nav.pop(); return true; } return false; }; render() { return ( <Navigator ref={ nav => { this.navigator = nav; }} initialRoute={{ name: "HomePage", component: HomePage }} configureScene={(route) => { return Navigator.SceneConfigs.PushFromRight; }} renderScene={(route, navigator) => { let Component = route.component; return <Component {...route.params} navigator={navigator} /> }} /> ); }}//這是一個使用了通用瀏覽的測試頁面class HomePage extends Component { toDetailPage(){ const { navigator } = this.props; if(navigator) { navigator.push({ name: 'DetailPage', component: DetailPage, params:{ rightTitle:"分享" } }) } } render(){ return ( <View style={{flex:1}}> <Nav {...this.props} ref='nav' title='通用瀏覽Home' backgroundColor='#e6454a'/> <TouchableOpacity onPress={this.toDetailPage.bind(this)} style={{backgroundColor:'#f2f2f2',marginTop:20,justifyContent:'center',alignItems:'center',}}> <Text style={{fontSize:28,color:'#998462',textAlign:'center',}}>跳轉詳情</Text> </TouchableOpacity> </View> ); }}AppRegistry.registerComponent('你自己的模組名', () => PageIndex);
RN頁面B代碼如下:DetailPage.js
'use strict';import React, { Component } from 'react';import { View, Text,} from 'react-native';import Nav from './Nav.js';export default class DetailPage extends Component { constructor(props) { super(props); let navigator = this.props.navigator; if (navigator) { let routes = navigator.getCurrentRoutes(); //nav是導航器對象 let lastRoute = routes[routes.length - 1]; // 當前頁面對應的route對象 lastRoute.handleBack = this.leftCallBack.bind(this);//設定route對象的hanleBack屬性 } } /** * 情境:編輯頁面,點擊物理或左上方返回,需要提示“確定放棄修改嗎。” */ leftCallBack(){ let logic = false;//你可以修改為true if(logic){ alert("我不想返回"); }else{ this.refs.nav.back(); } } render(){ return ( <View style={{flex:1}}> <Nav {...this.props} ref='nav' leftCallBack={this.leftCallBack.bind(this)} rightCallBack={()=>{alert('分享')}} title='通用瀏覽Detail' backgroundColor='#e6454a'/> <View style={{flex:1,backgroundColor:'#f2f2f2',justifyContent:'center',alignItems:'center',}}> <Text style={{fontSize:28,color:'#998462',textAlign:'center',}}>我只是容器裡的一個RN頁面</Text> </View> </View> ); }}
好,這樣就基本實現了通用的RN容器和導航,當然還有一些地方可以最佳化。