ios 原生 跳轉(zhuǎn)到不同的RN頁(yè)面

參考自github作者:ljunb 文章鏈接:https://github.com/ljunb/rn-relates/issues/2
這里感謝下這位作者!

前言

最近的RN項(xiàng)目中要引入融云sdk實(shí)現(xiàn)即時(shí)聊天鲁纠,這樣就需要原生與RN的混編了魄衅,現(xiàn)在大部分的技術(shù)點(diǎn)的都攻克了(僅ios端),遇到了很多問(wèn)題苍在,都值得記錄一下绝页,這里就先講一講在ios端跳轉(zhuǎn)到RN頁(yè)面的問(wèn)題吧,下面開(kāi)始:

一寂恬、初始方案

我的應(yīng)用中续誉,會(huì)話列表頁(yè)面是RN頁(yè)面,會(huì)話頁(yè)面則完全是一個(gè)原生頁(yè)面掠剑,直接用的融云的UI屈芜。在會(huì)話頁(yè)面中點(diǎn)擊導(dǎo)航欄右側(cè)按鈕想要跳轉(zhuǎn)到RN頁(yè)面去實(shí)現(xiàn),跳轉(zhuǎn)時(shí)還需要傳遞給RN當(dāng)前用戶id等參數(shù)朴译,我最初的方案如下:

新建一個(gè)RN應(yīng)用井佑,在代碼中我們可以看到,其用以下方法去創(chuàng)建加載RN應(yīng)用:

  NSURL *jsCodeLocation;
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"RNChatDemo"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];

可以看到眠寿,在初始化RCTRootView實(shí)例時(shí)躬翁,需要傳入moduleName和initialProperties這兩個(gè)參數(shù),先說(shuō)說(shuō)這2個(gè)參數(shù):

  • moduleName

在RN端的入口中盯拱,我們用AppRegistry的registerComponent方法來(lái)注冊(cè)組件盒发,其第一個(gè)參數(shù)對(duì)應(yīng)的便是ios中的moduleName,所以我們可以在RN入口處注冊(cè)多個(gè)組件狡逢,在ios端我們需要用的哪個(gè)RN組件時(shí)宁舰,便改變moduleName值來(lái)加載這個(gè)RN組件即可。

//AppRegistry的registerComponent方法
registerComponent(
    appKey: string,
    componentProvider: ComponentProvider,
    section?: boolean,
  )
  
  
//RN端入口index.js
AppRegistry.registerComponent('RNChatDemo', () => App);
AppRegistry.registerComponent('RNPage', () => RNPage);
  • initialProperties
    我們可以通過(guò)initialProperties可以向RN組件傳遞初始參數(shù):
NSMutableDictionary *initialProperty = [NSMutableDictionary dictionary];
[initialProperty setObject:self.targetId forKey:@"targetId"];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                    moduleName:@"RNPage"
                                             initialProperties:initialProperty
                                                 launchOptions:nil];

RN端直接用this.props接收:

render(){
        return(
            <View style={styles.container}>
                <Text style={{fontSize:20}}>{this.props.targetId}}</Text>
            </View>
        )
    }

所以我最開(kāi)始的方案是:創(chuàng)建多個(gè)ViewController,每個(gè)ViewController對(duì)應(yīng)加載一個(gè)RN的頁(yè)面奢浑,加載RN的方法即是上面的rootView的initWithBundleURL方法蛮艰,通過(guò)不同moduleName來(lái)加載不同的RN頁(yè)面,在viewDidLoad時(shí)加載雀彼,加載后賦給self.view即可壤蚜。

- (void)viewDidLoad {
  [super viewDidLoad];
  
   NSURL *jsCodeLocation;
    jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                        moduleName:@"RNPage"
                                                 initialProperties:nil
                                                     launchOptions:nil];
    self.view = rootView;
}

先看一下效果:
初始方案.gif

這就是最開(kāi)始的方案即寡,確實(shí)時(shí)可以實(shí)現(xiàn)的,但是可以看到袜刷,在調(diào)試時(shí)聪富,每次由原生跳轉(zhuǎn)到RN時(shí),都會(huì)有bundle加載的進(jìn)度條著蟹,進(jìn)度條加載完成后才將RN頁(yè)面加載出來(lái)墩蔓,估計(jì)打包后,每次跳轉(zhuǎn)會(huì)有一個(gè)短暫白屏的過(guò)程草则,這樣的話體驗(yàn)就不好了钢拧,所以尋求一個(gè)更好的方案,github中看到來(lái)作者ljunb的方案炕横,非常受用源内,這里再次感謝??,下面就開(kāi)始對(duì)這個(gè)方案進(jìn)行優(yōu)化

二份殿、方案優(yōu)化

我們首先剖析一下膜钓,打開(kāi)initWithBundleURL方法源碼,我們先看看在頭文件中的官方注釋:

/**
 * - Designated initializer -
 */
- (instancetype)initWithBridge:(RCTBridge *)bridge
                    moduleName:(NSString *)moduleName
             initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;

/**
 * - Convenience initializer -
 * A bridge will be created internally.
 * This initializer is intended to be used when the app has a single RCTRootView,
 * otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
 * to all the instances.
 */
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions;

注釋中說(shuō)的很明確了卿嘲,initWithBundleURL這個(gè)方法會(huì)先建一個(gè)RCTBridge的實(shí)例颂斜,方法適用于整個(gè)app只有一個(gè)RCTRootView的情況,在有多個(gè)RCTBridge的情況下拾枣,我們可以先建立一個(gè)全局的bridge沃疮,使用initWithBridge這個(gè)方法去展示RN,接下來(lái)我們具體看下initWithBundleURL的源碼梅肤,看看是不是這樣:

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions
{
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
                                            moduleProvider:nil
                                             launchOptions:launchOptions];

  return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
  
}

可以看到司蔬,源碼中便是使用的initWithBridge方法,如果我們像初始方案一樣姨蝴,每次都執(zhí)行initWithBundleURL方法俊啼,那么每次都會(huì)初始化一個(gè)RCTBridge實(shí)例,會(huì)占用更多的時(shí)間和資源開(kāi)銷左医,同時(shí)每次在用到RN頁(yè)面的時(shí)候才去加載bundle資源授帕,會(huì)有一段白屏?xí)r間,因此給出的優(yōu)化方案是:

  • 建立一個(gè)NSObject類浮梢,讓其實(shí)現(xiàn)RCTBridgeDelegate協(xié)議
  • 這個(gè)類添加一個(gè)bridge屬性作為一個(gè)全局的bridge跛十,每一次新建RN頁(yè)面使用這個(gè)bridge
  • 類中實(shí)現(xiàn)預(yù)加載方法,在適當(dāng)?shù)臅r(shí)候可以預(yù)加載RCTRootView
  • 類中實(shí)現(xiàn)RCTRootView的管理秕硝,將預(yù)加載的RCTRootView保存起來(lái)偶器,在用到的時(shí)候直接提取

這個(gè)類的具體的實(shí)現(xiàn)如下:

//ReactRootViewManager.h

#import <React/RCTRootView.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridge.h>

@interface ReactRootViewManager : NSObject<RCTBridgeDelegate>

/* 全局唯一的bridge */
@property (nonatomic, strong, readonly) RCTBridge * bridge;

/*
 * 獲取單例
 */
+ (instancetype)manager;

/*
 * 根據(jù)viewName預(yù)加載bundle文件
 * param:
 *     viewName RN界面名稱
 *     initialProperty: 初始化參數(shù)
 */
- (void)preLoadRootViewWithName:(NSString *)viewName;
- (void)preLoadRootViewWithName:(NSString *)viewName initialProperty:(NSDictionary *)initialProperty;

/*
 * 根據(jù)viewName獲取rootView
 * param:
 *     viewName RN界面名稱
 *
 * return: 返回匹配的rootView
 */
- (RCTRootView *)rootViewWithName:(NSString *)viewName;

@end

具體的.m文件實(shí)現(xiàn):

//ReactRootViewManager.m
#import "ReactRootViewManager.h"

@interface ReactRootViewManager ()
// 以 viewName-rootView 的形式保存需預(yù)加載的RN界面
@property (nonatomic, strong) NSMutableDictionary<NSString *, RCTRootView*> * rootViewMap;
@end

@implementation ReactRootViewManager

- (void)dealloc {
  _rootViewMap = nil;
  [_bridge invalidate];
}

+ (instancetype)manager {
  static ReactRootViewManager * _rootViewManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _rootViewManager = [[ReactRootViewManager alloc] init];
  });
  return _rootViewManager;
}

- (instancetype)init {
  if (self = [super init]) {
    _rootViewMap = [NSMutableDictionary dictionaryWithCapacity:0];
    _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
  }
  return self;
}

- (void)preLoadRootViewWithName:(NSString *)viewName {
  [self preLoadRootViewWithName:viewName initialProperty:nil];
}

- (void)preLoadRootViewWithName:(NSString *)viewName initialProperty:(NSDictionary *)initialProperty {
  if (!viewName && [_rootViewMap objectForKey:viewName]) {
    return;
  }
  // 由bridge創(chuàng)建rootView
  RCTRootView * rnView = [[RCTRootView alloc] initWithBridge:self.bridge
                                                  moduleName:viewName
                                           initialProperties:initialProperty];
  [_rootViewMap setObject:rnView forKey:viewName];
}

- (RCTRootView *)rootViewWithName:(NSString *)viewName {
  if (!viewName) {
    return nil;
  }
  return [self.rootViewMap objectForKey:viewName];
}

#pragma mark - RCTBridgeDelegate
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
}

@end

在我的項(xiàng)目中,我在需要預(yù)加載的VC的viewDidLoad中實(shí)現(xiàn)預(yù)加載:

//配置initialProperties
  NSMutableDictionary *initialProperties = [NSMutableDictionary dictionary];
  [initialProperties setObject: [RCTRongCloud _convertConversationType:self.conversationType] forKey:@"type"];
  [initialProperties setObject:self.targetId forKey:@"targetId"];
  
  //RN頁(yè)面預(yù)加載
  NSString *pageName = @"RNPage";
  [[ReactRootViewManager manager] preLoadRootViewWithName:pageName initialProperty:initialProperties];

在RN頁(yè)面所在的ViewController中,在viewDidLoad里將預(yù)加載的rootView賦給self.view :

//RNPageViewController.m
- (void)viewDidLoad {
  [super viewDidLoad];
  self.view=[[ReactRootViewManager manager] rootViewWithName:@"RNPage"];
}

最后,在由ios跳轉(zhuǎn)到RN的方法里屏轰,直接push上面的ViewController :

RNPageViewController *RNPageVC = [[RNPageViewController alloc] init];
[self.navigationController pushViewController:RNPageVC animated:YES];

ok,看一下效果吧:
優(yōu)化方案.gif

后面項(xiàng)目完成了我會(huì)對(duì)ios/RN集成融云憋飞,以及ios端與RN的交互作一些總結(jié)霎苗,有問(wèn)題歡迎評(píng)論探討,謝謝大家榛做。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唁盏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子检眯,更是在濱河造成了極大的恐慌厘擂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锰瘸,死亡現(xiàn)場(chǎng)離奇詭異刽严,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)避凝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)舞萄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人管削,你說(shuō)我怎么就攤上這事倒脓。” “怎么了含思?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵崎弃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我含潘,道長(zhǎng)饲做,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任调鬓,我火速辦了婚禮艇炎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腾窝。我一直安慰自己缀踪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布虹脯。 她就那樣靜靜地躺著驴娃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪循集。 梳的紋絲不亂的頭發(fā)上唇敞,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼疆柔。 笑死咒精,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旷档。 我是一名探鬼主播模叙,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鞋屈!你這毒婦竟也來(lái)了范咨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厂庇,失蹤者是張志新(化名)和其女友劉穎渠啊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體权旷,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡替蛉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炼杖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灭返。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坤邪,靈堂內(nèi)的尸體忽然破棺而出熙含,到底是詐尸還是另有隱情,我是刑警寧澤艇纺,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布怎静,位于F島的核電站,受9級(jí)特大地震影響黔衡,放射性物質(zhì)發(fā)生泄漏蚓聘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一盟劫、第九天 我趴在偏房一處隱蔽的房頂上張望夜牡。 院中可真熱鬧,春花似錦侣签、人聲如沸塘装。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹦肴。三九已至,卻和暖如春猴娩,著一層夾襖步出監(jiān)牢的瞬間阴幌,已是汗流浹背勺阐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矛双,地道東北人渊抽。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像议忽,于是被迫代替她去往敵國(guó)和親腰吟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 雙親日 今天徙瓶,父親節(jié)。這也許是我和父親能夠共同度過(guò)的最后一個(gè)父親節(jié)嫉称。40多年在一起的歲月深深烙在我的骨子里侦镇,無(wú)法割...
    繁星如海閱讀 276評(píng)論 1 2