如何做一个RN容器-iOS

1.如何引入RN

如何依赖

我们在用react-native init的时候,他会生成一个iOS的工程,进去后我们发现,他依赖的库都是以子工程的方式引入的,但是如果需要引入我们目前的工程,用pod管理依赖,这样的方式显然是不能接受的。在npm的管理包里面,我们会发现有一个react.podspec,所以许多人建议直接用podspec的path去引入,例如这样:

pod 'React', :path => '../node_modules/react-native',  
        :subspecs => [
            'RCTText',
         ]

但是在实际中,我们并不能控制别人的RN路径,所以这样的方法也是不能采用的。

这样就会想到,我们自己维护一个私有的react.podspec到我们的私有pod库里面,具体私有pod如何做请点击或者google百度.

结论: 自己维护一个react.podspec到自己的私有pod库里面,内容与RN官方差不多示例 ,打不开就自己脑洞

如何引入

使用pod引入 pod 'React' , 'xxx'

如何整个页面出来

需要三个参数: module , url , property

module 为RN业务生成或者注册的

url:看RN的示例里面,使用的是本地开发的方式,

[[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];

其他就是一个NSURL,把对应的jsbundle链接拿出来即可

property: 初始化的属性字典,可以为空

最后:即可生成一个rn的view,加入到vc或者需要的地方就好了

[[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                        moduleName:self.module
                                                 initialProperties:self.rnProperty
                                                     launchOptions:nil];

结论 : pod 依赖自己的私有react库,然后走示例的工程RCTRootView就好了

2.如何导入native组件

工程建好了,我们的RN业务需要一个当前项目用户的登录信息怎么办?

RN官方文档也介绍了如何写native给rn用的的模块,简单介绍一下

  1. 写一个类,假如叫UserRN,先加入协议:RCTBridgeModule,
  2. 在UserRN.m里@implementation先RCTEXPORTMODULE(xxx)导出rn用的名字
  3. 如果是全生命周期不变的数据可以导出为静态变量给rn,实现方法- (NSDictionary<NSString *,id> *)constantsToExport就好了
  4. 如果可能会变化,比如登录信息,可以用block返回给rn,当rn调用方法的时候RCT_EXPORT_METHOD(getAppData:(RCTResponseSenderBlock)callback)
  5. 到这里一个组件就基本完成了,实现的是如何给出用户登录信息

如果需要导出控件或者视图,参照rn官方文档,都差不多。

3.如何与RN通信

  • rn到原生:

    就用2里面的方式就可以了

  • 原生到rn:

    如果在原生导出给rn的组件里面:可以在implementation里面用 @synthesize bridge = _bridge;获取到当前bridge, 用bridge发送信息给rn:

            NSString *name = @"shareResult";
            NSDictionary *body = @{@"result":[NSNumber numberWithBool:result]};
            [self.bridge enqueueJSCall:@"RCTNativeAppEventEmitter"
                                method:@"emit"
                                  args:body ? @[name, body] : @[name]
                            completion:NULL];

在rn那边,只需要监听这个就可以了,示例:

NativeAppEventEmitter.addListener(  
         'EventReminder',
          (reminder) => {
            let errorCode=reminder.errorCode;
            //msg:reminder.name获取获取的消息
            }
          }
         );

over

4.如何管理bundle

这里我只想介绍一个本地内置jsbundle的方式,不做客户端引入jsbundle的版本如何控制的阐述,何如进行缓存或者更新的阐述。

目的:减少原工程集成流程和配置,引入jsbundle到工程,减少下载的时间成本。

方式:通过布在服务器上的脚本,去生成包含jsbundle的资源私有pod库。

客户端引入:pod 'xxx' , 'x.x.x'

大概实现:

  1. 后台配置网站或者其他工具, 生成某个app需要依赖的jsbundle的配置文件
  2. 定时跑一个拉取jsbundle的脚本,修改配置文件可以主动调用
  3. 拉取下来的jsbundle放入pod私有工程里面里面,脚本自动打包成私有库,并提升版本上传到pod仓库
  4. 工程依赖的时候只需要修改这个pod的库

5.如何打native包调试

在rn的源码中,大量的宏定义用到了DEBUG这个系统提供的宏,但是当我们打包ipa的时候,默认选择的是release包,所以debug功能消失,比如真机摇一摇。有人说那我直接连着线装进去不就好了,这样也可以,但是考虑到团队人数和多端开发熟悉技能,各个工程权限的问题,不推荐。

这里介绍一种简单的方式:

  1. 选择运行target里面的editScheme
  2. 选择Archive
  3. buildConfiguration -> 改成 DEBUG

可以自己新生成一个scheme,这样的好处是不破坏原工程的配置,这样打出来的ipa可以使用RN的所有有关debug环境下的功能,上线时需要关闭。

6.如何优化加载速度

  1. 内置jsbundle到工程里面来
  2. 预加载RCTBridge
  3. 多次用到的同一个rn,可以复用,不要一下就释放了

7.如何处理webview容器交叉的功能

假如,rn和webview都用到了原生的拍照或者选择图片,如何实现给各个容器,不必重复劳动,不用维护两套代码。

示意图

具体实现自己考量

8.闲谈RN其他东西

这里写一些自己踩的原生使用RN坑

  1. use_frameworks

由于公司有使用swift,所以use_frameworks时必须的,但是你使用的时候会发现,在React 0.40.0版本及之前,CSSLayout会提示找不到文件,CSSLayout文件重复这些错误, 0.40.0之后会提示 yoga jshelper等找不到,各种文件重复的问题,这里喔给出0.39.x的解决方法

在podfile这样使用: 删除重复文件,及修改头文件内容

pre_install do |installer|  
    # Yoga exists in two places in the RN source tree. Delete the obsolete one to avoid redefinitions
    `rm -rf ./Pods/React/React/CSSLayout`
end

def patch_subspecs(installer)  
    # patch source and header files
    `find ./Pods/React/React ./Pods/React/ReactCommon ./Pods/React/Libraries \\( -name *.h -o -name *.m -o -name *.mm \\) -print0 | xargs -0 sed -i '' -e 's:<CSSLayout/\\(.*\\)>:\"\\1\":g'`

    # exclude duplicate symbols
    installer.pods_project.targets.each do |target|
        next unless target.name == 'React'

        source_files = target.source_build_phase.files
        uniqs = source_files.uniq { |f| f.file_ref.path }
        (source_files - uniqs).each { |dup| source_files.delete(dup) }
    end
end

2.调试的时候,RCTDevMenu重复出现

原因: 每个RCTRootView都接受摇一摇的通知,并且所有配置都是共同使用的,所以导致重复出现。这时候你不能显示本地的jsbundle,不然会出现找不到url的错误。

3.在加载时间显示loading的过渡效果

  1. 可以自己实现一套,在rn显示完成后,然后rn调你的隐藏方法
  2. 用RCTRootView.loadingView

推荐第二种,简单。

  1. 如果自己集成推送

需要将推送事件发给rn,rn发送通知需要bridge,可以在实例RCTRootView的类里面实现notifications接受,然后通过与rn通信发给rn。

需要用相同方法把自己的生命周期给rn

  1. 暴露出来给收集奔溃bug

使用RCTSetFatalHandler(^(NSError *error) {},这个error的userinfo可能不能被序列化,所以需要注意

6.找到rctrootview所在的vc

微信扫描查看或分享
加入我们