Home

写给前端的 iOS 开发教程(3):iOS 与 ReactNative 混合开发

分类: iOS

ReactNative 是啥我就不具体介绍了,作为前端你没用过也该听说过。从上半年开始一直断断续续写 RN,遇到 RN 和原生混合开发场景,当然 RN 理论上来说也可以理解为原生应用,只不过中间多了一层 JS 的编译,但是我这里的原生指的是 Objective-C 的原生 iOS 开发。

作为前端来讲从 RN 来学习 iOS 开发我觉得再合适不过了,下面就是一个 RN 结合原生的开发例子。

假设你已经有了一个原生应用,然后通过 react-native init 开始一个新的 RN 项目工程,把你的原生应用的代码全部放到 RN 项目的 ios 目录里面去。理论上来讲你的原生项目应该用了CocoaPods 来管理一些库,这样你可以在 pod 里面把 RN 的一些原生依赖模块加进去,比如:

target '项目名字' do
    pod 'React', :path => '../node_modules/react-native', :subspecs => [
        'Core',
        'RCTText',
        'RCTImage',
        'RCTNetwork',
        'RCTWebSocket', # needed for localhost testing of your app
        # Add any other subspecs you want to use in your project
    ]
end

然后在 ios 目录下面 pod install 安装这些依赖,现在你应该把 RN 集成到你的 原生项目里了,但是并没有什么用,因为两者之间还没什么通信,我们还需要从原生页面进入 RN 页面,从 RN 页面回到原生页面,这里有个问题,就是如果你的 RN 页面只是一级的话那么可以直接调用原生的导航了,但是如果你的 RN 页面里面还有其他的 RN 子页面,那么我建议是在 RN 页面之间的跳转用 RN 的导航(Navigator),在 RN 页面返回到原生页面的时候调用一个原生提供的跳转方法。

假如你的 RN index.ios.js 文件如下所示:

import React from 'react'
import {
  AppRegistry,
} from 'react-native'
import { Provider } from 'react-redux'
// 把 rn 相关的代码放在 reactnative 目录下面
import Navigation from './reactnative/components/Navigation'
import configureStore from './reactnative/store/configureStore'
const store = configureStore()
class RnEntry extends React.Component {
  render() {
   
    return (
      <Provider store={store}>
        <Navigation />
      </Provider>
    )
  }
}

// AppRegistry.registerComponent('SimpleView', () => SimpleView)
AppRegistry.registerComponent('RnEntry', () => RnEntry)

如果你只注册了一个 RN 入口 RnEntry (如果你想在原生里面添加不同的 RN 入口可以注册不同的组件模块),那么你在原生代码设置 RN 入口的地方可以这么写:

#import "RNViewController.h"
#import "RCTRootView.h"
....
- (void)goRN {
// WithDrawEntry 就是 RN 页面注册的模块名
// 原生页面传给 RN 页面的参数 initialProperties,在 RN 页面通过 this.props.userId 来获取 
    NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                        moduleName: @"WithDrawEntry"
                                                 initialProperties: @{
                                                                        @"userId": TheUserService.userInfo.userId,
                                                                      @"userCode": TheUserService.userInfo.userCode,
                                                                      @"token": TheUserService.userInfo.token,
                                                                      }
                                                     launchOptions:nil
                             ];
    
    RNViewController *withDrawViewVC = [[RNViewController alloc] init];
    [self.navigationController setNavigationBarHidden:YES];
    withDrawViewVC.view = rootView;
// 隐藏原生的顶部导航,在 RN 里面用 RN 提供的导航
    [self.navigationController pushViewController:withDrawViewVC animated:YES];
}

@end

然后为了方便管理代码,我们新建一个 RNModule 的目录放提供给 RN 的模块,在 RNModule 下面我们新建几个文类 1.RNViewController.m


#import "RNViewController.h"
#import "RCTLog.h"

@interface RNViewController ()

@end

@implementation RNViewController

- (instancetype)init {
  self = [super init];
  if (self) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(popRN) name:@"popRN" object:nil];
  }
  return self;
}

- (void)viewDidLoad {
  [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)popRN {
// 提供一个从 RN 页面返回原生页面的方法
  dispatch_async(dispatch_get_main_queue(), ^{
      [self.navigationController popViewControllerAnimated:YES];
      [self.navigationController setNavigationBarHidden:NO];
  });
}


@end

2.RNViewController.h


#import <UIKit/UIKit.h>
#import "RCTRootView.h"

@interface RNViewController : UIViewController

@end

3.RNNotificationManager.h //管理 原生 RN 之间的通信


#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"

@interface RNNotificationManager : NSObject<RCTBridgeModule>

@end

4.RNNotificationManager.m


#import "RNNotificationManager.h"
#import "RCTLog.h"

@implementation RNNotificationManager

RCT_EXPORT_MODULE();


RCT_EXPORT_METHOD(postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo)
{
  RCTLogInfo(@"postNotification name: %@ userInfo %@", name, userInfo);
  [[NSNotificationCenter defaultCenter] postNotificationName:name object:self userInfo:userInfo];
}

@end

到这里原生的代码就写的差不多了,接下来就是在 RN 的 js 代码里面引用了,假如你从原生进入 RN 的第一个页面为 Home.js:

import React, { PropTypes } from 'react'
import {
  StyleSheet,
  Text,
  Platform,
  View,
  TouchableOpacity,
  NativeModules,
} from 'react-native'
class Home extends React.Component {
	constructor(props) {
    super(props) 
  }
	...
	goToNativeView() {
    Platform.OS === 'ios' ? NativeModules.RNNotificationManager.postNotification('popRN', {}) : null // 安卓的暂时不演示
  }
  render() {
   ...
   return (
   <TouchableOpacity
       onPress={this.goToNativeView}
    >
     <View>
     <Text>回到原生页面</Text>
    </View>
    </TouchableOpacity>
   )
  }
}
...