React Native View布局

React-Native 之布局UIKit(iOS)

本文探讨一下React-Native(以下简称RN)iOS端的布局过程。

这个过程包括RN从App(模块)启动到最后JSX的View映射成UIKit的View渲染。这里我把这个过程分为两步,第一步加载rootView,第二步rootView加载JSX的view

先看第一步加载RootView:

开始

第一步RootView通常会在程序里这么初始化的。

1
2
3
4
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"RN_UI"
initialProperties:nil
launchOptions:launchOptions];
  1. 这里用RootView做为一个容器,可以理解为webView加载网络资源jsCodeLocation
  2. 初始化JS与OC的方法表,这方法表可以理解为他们通信的桥梁。具体原理可以看一下bang的这篇博客
  3. 初始化第二部的桥梁,加载OC的本地模块并告诉JS模块
  4. 同时UI上显示一个Loading 远程资源的状态。
  5. 等到环境构建完后(远程资源加载到本地内存,JS加载完OC本地的模块)
  6. 初始话rootView的contentView,在RCTUIManager里设置它作为rootShadowView,关于shadowView后面会再作介绍,这里暂且当它为一个普通的UIView好了。这里面的UI更新频率是根据CADisplayLink的频率来对JS做batchUpdate的。
  7. RCTRootView通知JS运行JS程序。
  8. 开始渲染index.ios.js里的入口程序。
  9. RCTRootView停止LoadingView状态,后续内容的展现全是JS的程序逻辑了。

第二步,UIView怎么通过JSX来实现布局的。

先来埋个伏笔:React-Native是用css3的Flexbox来实现布局的,UIKit要不就是绝对布局(setFrame)要不就是autoLayout,最后UIKit是怎么用的JS里的Flexbox呢?

来看一个简单的例子:

1
2
3
4
5
6
7
8
<View style={{flexDirection: 'column'}}>
{shopItem.isTopFloor && CommonComponents.renderFloorHeader(shopItem.floorName)}
<View style={styles.floorDoubleItemContainer}>
<SmallShopComponent shopItem={leftItem} />
<View style={{width: 5}}></View>
<SmallShopComponent shopItem={rightItem} />
</View>
</View>

这里的语法和HTML很像,只不过Dom节点变成了react似的虚拟Dom节点样式。

看看JS里的View是怎么实现的?

1
2
3
4
var RCTView = createReactNativeComponentClass({
validAttributes: ReactNativeViewAttributes.RCTView,
uiViewClassName: 'RCTView',
});

这里的createReactNativeComponentClass通过调用堆栈最后是调用到ReactNativeBaseComponentmountComponent。这里有个很关键的RCTUIManager.createView

RCTUIManager是什么?它OC本地的一个的桥接模块,这个模块初始化的时候会把所有RCTViewManager的子类都收集起来放到一个_componentDataByName

JS里面每个特定的基础View模块(比如MapView, WebView)一般都是由OC里的某个特定的ViewManager桥接过来的,如果不是,那也是这些桥接的View的组合(比如ListView)。

重点来了,当JS调用OC的createView:viewName:rootTag:props:后,OC本地会创建一个RCTShadowView

RCTShadowView看它的基类,它并非一个UIView,而是一个结合layout.c的,这个layout.c文件是Flexbox布局算法的c语言实现。

最后从js传来的Flexbox属性会交给这个shadowViewcss_node处理。在前面第一步RootView初始话的第六步里,batchUpdate里,在主线程把所有的shadowViewflexbox模型的计算结果布局到真正的View上面。

大致流程是这样的。