现有iOS项目中嵌入几个React Native页面

写文章

发现

关注

消息2

现有iOS项目中嵌入几个React Native页面

nilcy关注

2016.12.20 15:19 *字数1500阅读1631评论5喜欢25

1.搭建环境

具体步骤参考官方文档,环境弄好后,工程目录如下

原的iOS项目被放在了根目录的iOS的文件夹下(没做安卓,所以没有安卓的路径)

React Native的iOS入口是index.ios.js

其他React Native的代码放在了组件文件夹

main.jsbundle为我们所写的React Native代码的集合,发布时才生成(或方便真机调试)

2.入口

RN入口index.ios.js

'use strict'; //使用严格模式import React, { Component } from 'react';import {AppRegistry,//用于注册组件StyleSheet,//使用样式Text,View,Image,NavigatorIOS,//导航控制器TouchableHighlight,//点击效果NativeModules//调用native方法} from 'react-native';import Repayment from './component/repayment';import SettlementAccountList from './component/SettlementAccountList';export default class MECRM extends Component {_handleNavigationBackRequest() {var RNBridge = NativeModules.RNBridge;RNBridge.back();}_settlementAccountList() {var status = this.props["status"];if (status === 0 || status === 3) {this.refs['nav'].push({title: '返款人信息表',component: SettlementAccountList,barTintColor: '#7B9DFD',tintColor: 'white',passProps: {}})}}render() {return ( this._handleNavigationBackRequest(),onRightButtonPress: () => this._settlementAccountList(),passProps: {orderid: this.props["orderid"],status: this.props["status"],price: this.props["price"]},barTintColor: '#7B9DFD'}}style={{flex: 1}}itemWrapperStyle={styles.itemWrapper}tintColor="white"titleTextColor ='white'/>);}}const styles = StyleSheet.create({container: {flex: 1,justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF',},welcome: {fontSize: 20,textAlign: 'center',margin: 10,},instructions: {textAlign: 'center',color: '#333333',marginBottom: 5,},});AppRegistry.registerComponent('RNBackApply', () => MECRM);

index.ios.js作为RN代码入口,关键点是NavigatorIOS标签:

ref ='nav',把NavigatorIOS对象标记为'nav'方便调用,类似iOS开发中的标签,this.refs ['nav']便能找到NavigatorIOS对象(弥补这个传递的麻烦)

initialRoute初始化路由,这里初始化起始页为还款,然后点击左右按钮分别执行handleNavigationBackRequest(返回原始页面),settlementAccountList(跳转到返款账号列表页面)

passProps,传递顺序,状态,价格到偿还页面(此处这3个参数是从naive传递到index.ios.js,index.ios.js再传递给Yet还款)

本地入口

let jsCodeLocation = URL(string: "http://localhost:8081/index.ios.bundle?platform=ios")let mockData:NSDictionary = ["orderid": self.orderId,"status" : self.orderDetailModel.status,"price"  : self.orderDetailModel.cost]let rootView = RCTRootView(bundleURL: jsCodeLocation,moduleName: "RNBackApply",initialProperties: mockData as [NSObject : AnyObject],launchOptions: nil)let vc = UIViewController()vc.view = rootViewself.navigationController?.isNavigationBarHidden = trueself.navigationController?.pushViewController(vc, animated: true)

jsCodeLocation RN执行文件路径,这里的路径为开发时使用,发布时需要更换为main.jsbundle的路径

mockData为从native传递到RN的数据

moduleName: "RNBackApply"与index.ios.js中registerComponent('RNBackApply', () => MECRM)对应

3.构建页面

前面提到起始页为Repayment,那么Repayment是怎么实现如上图的喃?以下为简要实现

'use strict';import React, { Component } from 'react';import {View,Text,StyleSheet,ScrollView,TouchableHighlight,//整块区域有按压效果AlertIOS,TouchableOpacity,//文字有按压效果TextInput,Image,NativeModules,DeviceEventEmitter//通知} from 'react-native';import PayTypeChoice from './PayTypeChoice';//注意路径是以当前文件为准export default class repayment extends Component {constructor(props) {super(props);var defaultMoney = (this.props["price"]*0.2<0.01?0.01:this.props["price"]);this.state = {events: {info: {id: '',orderId: this.props["orderid"].toString(),account: '',accountType: 1,accountName: '',bankName: '',branchName: '',money: defaultMoney.toString(),status: '',remark: '',failReason: '',}}};this._applySettlementRequest();this._accountInfoChoiced();}_accountInfoChoiced() {this.subscription = DeviceEventEmitter.addListener('accountInfoChoiced',(accountInfo) => {var newEvents = this.state.events;newEvents.info.account = accountInfo.account;newEvents.info.accountName = accountInfo.accountName;newEvents.info.accountType = accountInfo.accountType;newEvents.info.bankName = accountInfo.bankName;newEvents.info.branchName = accountInfo.branchName;this.setState({events: newEvents});})}_renderRow(title: string, subTitle: string, placeholder: string, onChangeText: Function, maxLength: int) {var status = this.props["status"];return ({title}{(status === 0 || status === 3)?:});}......_renderButton(onPress: Function) {var status = this.props["status"];var buttonString = '申请返款';switch (status) {case 0:var buttonString = '申请返款';break;case 1:var buttonString = '返款处理中';break;case 2:var buttonString = '已返款';break;case 3:var buttonString = '已拒绝,重新申请';break;case 4:var buttonString = '待审核';break;}var canPost = false;var orderInfo = this.state.events.info;if ((status === 0 || status === 3) && orderInfo.accountName.length > 0 && orderInfo.account.length > 0 && orderInfo.money.length > 0 ) {if (orderInfo.accountType === 2) {if (orderInfo.bankName.length > 0 && orderInfo.branchName.length > 0) {canPost = true;}} else {canPost = true;}}return ({canPost?{buttonString}:{buttonString}});}_onButtonPress() {var orderInfo = this.state.events.info;orderInfo.money = Number(orderInfo.money*100);if(isNaN(orderInfo.money)){AlertIOS.alert('请输入正确的返款金额',)return;}var orderPrice = (this.props["price"]*100);if (orderInfo.money > orderPrice*0.8) {AlertIOS.alert('','当前返款大于支付金额的80%,是否继续?',[{text: '返回修改'},{text: '继续发起', onPress: () => {var RNBridge = NativeModules.RNBridge;RNBridge.setSettlement(orderInfo,(error, events) => {if (error) {// console.error(error);} else {this._handleNavigationBackRequest();}})}}])return;}var RNBridge = NativeModules.RNBridge;RNBridge.setSettlement(orderInfo,(error, events) => {if (error) {// console.error(error);} else {this._handleNavigationBackRequest();}})};_applySettlementRequest() {var status = this.props["status"];// status参数说明// 0    未申请// 1    返款中// 2    返款成功// 3    返款失败if (status === 0) {return}var RNBridge = NativeModules.RNBridge;var orderid = this.props["orderid"].toString();RNBridge.applySettlement(orderid,(error, events) => {if (error) {console.error(error);} else {events.info.money = (events.info.money/100).toString();this.setState({events: events});}})}render() {var orderInfo = this.state.events.info;var status = this.props["status"];return ({this._renderPayTypeRow(() => {this.props.navigator.push({title: '返款方式',component: PayTypeChoice,barTintColor: '#7B9DFD',tintColor: 'white',passProps: {accountType: this.state.events.info.accountType,getPayType:(accountType)=>{var newEvents = this.state.events;newEvents.info.accountType = accountType;this.setState({events: newEvents});}}})})}{this._renderRow('姓名', orderInfo.accountName, '请输入姓名', (accountName) => {var newEvents = this.state.events;newEvents.info.accountName = accountName;this.setState({events: newEvents});},10)}{(orderInfo.accountType === 2)?{this._renderRow('开户银行', orderInfo.bankName, '请输入开户银行', (bankName) => {var newEvents = this.state.events;newEvents.info.bankName = bankName;this.setState({events: newEvents});})}{this._renderRow('开户支行', orderInfo.branchName, '请输入开户支行', (branchName) => {var newEvents = this.state.events;newEvents.info.branchName = branchName;this.setState({events: newEvents});})}:null}......{this._renderButton(() => {this._onButtonPress();})});}}const styles = StyleSheet.create({......});

constructor 初始化数据,这里的数据结构和网络请求结果保持一致。

renderRow 函数以及被省略掉的其他renderXXXRow函数只是让总的render函数没那么臃肿,返回一些JSX片段,在构建界面中根据不同条件展示不同样式是常见需求,但是JSX中不支持 if.else,只支持三目运算符?:,在上面代码中多次用到。

需求功能1:点击返款方式,跳转到返款方式选择页面,然后把选择的方式回传。

这里页面传值采用的方式是将修改返款方式后的操作作为一个函数传递给下一个页面,实现如下。

Repayment.js

{this._renderPayTypeRow(() => {this.props.navigator.push({title: '返款方式',component: PayTypeChoice,barTintColor: '#7B9DFD',tintColor: 'white',passProps: {accountType: this.state.events.info.accountType,getPayType:(accountType) => {var newEvents = this.state.events;newEvents.info.accountType = accountType;this.setState({events: newEvents});}}})})}

PayTypeChoice.js

render() {return ({this._renderRow('支付宝', this.state.alipay,() => {this.props.getPayType(1);this.props.navigator.popToTop()})}{this._renderRow('银行卡', this.state.bankcard,() => {this.props.getPayType(2);this.props.navigator.popToTop()})});}

Repayment.js中的getPayType就是传递到下一个页面,当返款方式选择后以执行的函数。在PayTypeChoice.js中当cell点击的时候将返款方式作为参数传入,例如this.props.getPayType(1),就将返款方式设置为了支付宝。

需求功能2:点击右上角图标,跳转到返款账号列表页,然后把选择的账号信息带回来填充页面。

如之前所述,点击图标跳转的逻辑是写在index.ios.js文件中的

_settlementAccountList() {var status = this.props["status"];if (status === 0 || status === 3) {this.refs['nav'].push({title: '返款人信息表',component: SettlementAccountList,barTintColor: '#7B9DFD',tintColor: 'white',passProps: {}})}}

想通过刚才传递函数的方式达到页面传值,那么index.ios.js就要先获取到Repayment用于回调的函数,然后再传递给SettlementAccountList。很麻烦,并且我尝试了一下没成功。这种时候,通知就显得非常简单粗暴了,运用React Native中的通知组件DeviceEventEmitter,页面传值都不是事儿。

当账号信息被选择时在SettlementAccountList中发送通知

_onPressCell(rowData: string) {this.props.navigator.popToTop()DeviceEventEmitter.emit('accountInfoChoiced', rowData);}

在Repayment中接收通知

_accountInfoChoiced() {this.subscription = DeviceEventEmitter.addListener('accountInfoChoiced',(accountInfo) => {var newEvents = this.state.events;newEvents.info.account = accountInfo.account;newEvents.info.accountName = accountInfo.accountName;newEvents.info.accountType = accountInfo.accountType;newEvents.info.bankName = accountInfo.bankName;newEvents.info.branchName = accountInfo.branchName;this.setState({events: newEvents});})}

需求功能3:在进入页面时拉取之前填写的返款信息,点击左上的返回按钮回到Native页面,以及返款账号信息页面拉取已有的信息。这3点都是调用的Native方法。虽然RN也有网络请求方法,但是APP中的网络请求会有公共参数、公共的鉴权方法、错误处理等,所以网络请求还是选择走Native的好。

创建待RN调用的Native方法的步骤,在官方文档中也讲得很清楚,这里贴出我写的代码片段(因为Objective-C写着更方便就没用Swift,偷懒了一下)

RNBridge.m

#import "RNBridge.h"#import #import @implementation RNBridgeRCT_EXPORT_MODULE();RCT_EXPORT_METHOD(back){dispatch_async(dispatch_get_main_queue(), ^{UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];UINavigationController *navi = [tabvc selectedViewController];navi.navigationBarHidden = NO;[navi popViewControllerAnimated:YES];});}//获取返款信息RCT_EXPORT_METHOD(applySettlement:(NSString *)orderID callback:(RCTResponseSenderBlock)callback){dispatch_async(dispatch_get_main_queue(), ^{UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];UINavigationController *navi = [tabvc selectedViewController];UIViewController * vc = navi.viewControllers.lastObject;[vc startAnimating];NSString *path = [NSString stringWithFormat:@"/order/%@/applySettlement",orderID];[NetworkTool GET:path parameters:nil successHandler:^(id _Nonnull result) {[vc stopAnimating];callback(@[[NSNull null], result]);} failureHandler:^(NSError * _Nullable error) {[vc stopAnimating];callback(@[error.localizedDescription, [NSNull null]]);}];});}//设置返款信息RCT_EXPORT_METHOD(setSettlement:(NSDictionary *)orderInfo callback:(RCTResponseSenderBlock)callback){dispatch_async(dispatch_get_main_queue(), ^{UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];UINavigationController *navi = [tabvc selectedViewController];UIViewController * vc = navi.viewControllers.lastObject;[vc startAnimating];NSString *orderID = orderInfo[@"orderId"];NSString *path = [NSString stringWithFormat:@"/order/%@/applySettlement",orderID];[NetworkTool POST:path parameters:orderInfo successHandler:^(id _Nonnull result) {[vc stopAnimating];callback(@[[NSNull null], result]);} failureHandler:^(NSError * _Nullable error) {[vc stopAnimating];callback(@[error.localizedDescription, [NSNull null]]);}];});}//返款人信息表RCT_EXPORT_METHOD(getSettlementAccount:(NSInteger)start callback:(RCTResponseSenderBlock)callback){dispatch_async(dispatch_get_main_queue(), ^{UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];UINavigationController *navi = [tabvc selectedViewController];UIViewController * vc = navi.viewControllers.lastObject;[vc startAnimating];NSString *path = [NSString stringWithFormat:@"/order/getSettlementAccount?start=%ld&limit=%d",(long)start,100];[NetworkTool GET:path parameters:nil successHandler:^(id _Nonnull result) {[vc stopAnimating];callback(@[[NSNull null], result]);} failureHandler:^(NSError * _Nullable error) {[vc stopAnimating];callback(@[error.localizedDescription, [NSNull null]]);}];});}

在RN中调用返回方法

_handleNavigationBackRequest() {var RNBridge = NativeModules.RNBridge;RNBridge.back();}

在RN中获取已填写的返款信息

_applySettlementRequest() {var status = this.props["status"];// status参数说明// 0    未申请// 1    返款中// 2    返款成功// 3    返款失败if (status === 0) {return}var RNBridge = NativeModules.RNBridge;var orderid = this.props["orderid"].toString();RNBridge.applySettlement(orderid,(error, events) => {if (error) {console.error(error);} else {events.info.money = (events.info.money/100).toString();this.setState({events: events});}})}

4.调试

在模拟器中 command+D 调出RN的菜单,点击Debug JS Remotely。

在需要调试的代码前面加debugger,例如

_onButtonPress() {debuggervar orderInfo = this.state.events.info;orderInfo.money = Number(orderInfo.money*100);if(isNaN(orderInfo.money)){AlertIOS.alert('请输入正确的返款金额',)return;}......};

简陋,够用ლ(°◕‵ƹ′◕ლ)

5.上线以及热更新

上线的时候需要将代码中的jsCodeLocation修改一下

//let jsCodeLocation = URL(string: "http://localhost:8081/index.ios.bundle?platform=ios")let jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")

这个main.jsbundle是需要手动生成的,生成方法如下:

1.在React Native项目根目录下运行 npm start2.使用curl命令生成 main.jsbundlecurl http://localhost:8081/index.ios.bundle -o main.jsbundle

这样进入RN页面的时候,顶上就不再有提示信息了。如果提示找不到这个main.jsbundle文件,记得把main.jsbundle拖到iOS工程中引用一下。

打开main.jsbundle文件,你会发现里面包含了你所写的所有JS文件内容。所以其实你写的RN逻辑全在这里面。那么RN的热更新就很好理解了,更新这个文件就好了。不管你自己实现还是选择什么第三方热更新方案,都是在各种花式更新这个main.jsbundle文件而已。

6.一些补充和问题(碎碎念)

之前只讲了推跳转页面,模态喃?举例一发

{alert("closed")}}>Hello World! {this.setModalVisible(!this.state.modalVisible)}}>Hide Modal {this.setModalVisible(true)}}>Show Modal

另外补充一个小问题,如果npm start命令不好使的时候,尝试一下react-native start

还有一个遗留问题,返回原始页面的时候我是调用的本地方法返回的,难道RN自己不能返回吗,我其实是尝试这样的,也觉得这很理所当然。打印本地的navi.viewControllers,最后的的UIViewController就是RN页面,NavigatorIOS的流行方法调用了竟然没反应,难道是我姿势不对?

(lldb) po navi.viewControllers<__NSArrayI 0x600000254160>(,,)

RN官方提供了NavigatorIOS和导航仪,使用方法大同小异,总感觉带个iOS版更贴近原生。不过都说导航更凉爽。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容