我們知道JS主要管理的是界面渲染邏輯和事件處理邏輯瘤泪,那么渲染是怎么同步到Native端的呢灶泵?初始又是怎么創(chuàng)建的呢?
RCTRootView是入口对途,RCTRootView是RN的根視圖赦邻,內(nèi)部持有了一個(gè)RCTBridge,但是這個(gè)RCTBridge并沒有太多的代碼,而是持有了另一個(gè)RCTBatchBridge對(duì)象实檀,大部分的業(yè)務(wù)邏輯都轉(zhuǎn)發(fā)給BatchBridge惶洲,BatchBridge里面寫著的大量的核心代碼
RCTUIManager是管理所有UI的對(duì)象按声,負(fù)責(zé)創(chuàng)建,更新UIView對(duì)象恬吕。這樣就借助了 iOS本身的UIView 渲染機(jī)制進(jìn)行渲染签则。
RCTUIManager又是通過上篇所提到的通信機(jī)制作為一個(gè)JS對(duì)象來(lái)操作的。
在JS里面有一個(gè)所有JSComponent的tag表铐料,在OC里面依然也有這么一個(gè)所有nativeView的Tag表_shadowViewRegistry渐裂,只有通過唯一指定的tag,這樣RCTUIManager钠惩,才能知道到底應(yīng)該操作哪一個(gè)nativeView
一柒凉、創(chuàng)建rootview
在開始正式創(chuàng)建RCTRootView的時(shí)候會(huì)創(chuàng)建一個(gè)subviewRCTContentRootView這個(gè)東西創(chuàng)建的時(shí)候需要一個(gè)reactTag,這個(gè)tag是一個(gè)很關(guān)鍵的東西篓跛,此時(shí)通過allocateRootTag方法創(chuàng)建了root得reactTag膝捞。
- (NSNumber *)reactTag
{
RCTAssertMainQueue();
if (!super.reactTag) {
/**
* Every root view that is created must have a unique react tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc
*
* NOTE: Since the bridge persists, the RootViews might be reused, so the
* react tag must be re-assigned every time a new UIManager is created.
*/
self.reactTag = [_bridge.uiManager allocateRootTag];
}
return super.reactTag;
}
從注釋可以看出規(guī)則是從1開始,每次創(chuàng)建一個(gè)RootView實(shí)例都會(huì)累加10愧沟,如1蔬咬,11,21央渣,31计盒,以此類推渴频。創(chuàng)建完RCTContentRootView后還要去UIManager用這個(gè)reactTag注冊(cè)View芽丹,也就是以Tag為Key,登記進(jìn)入_viewRegistry字典表卜朗,同時(shí)創(chuàng)建對(duì)應(yīng)的shadow view拔第。
- (void)registerRootView:(RCTRootContentView *)rootView
{
NSNumber *reactTag = rootView.reactTag;
UIView *existingView = _viewRegistry[reactTag];
CGSize availableSize = rootView.availableSize;
_viewRegistry[reactTag] = rootView;
dispatch_async(RCTGetUIManagerQueue(), ^{
if (!self->_viewRegistry) {
return;
}
RCTRootShadowView *shadowView = [RCTRootShadowView new];
shadowView.availableSize = availableSize;
shadowView.reactTag = reactTag;
shadowView.backgroundColor = rootView.backgroundColor;
shadowView.viewName = NSStringFromClass([rootView class]);
self->_shadowViewRegistry[shadowView.reactTag] = shadowView;
[self->_rootViewTags addObject:reactTag];
});
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification
object:self
userInfo:@{RCTUIManagerRootViewKey: rootView}];
}
關(guān)于 shadow view,后面會(huì)有更詳細(xì)的介紹(如果有后面的話_)场钉,簡(jiǎn)而言之就是優(yōu)化布局用的蚊俺。
/**
* ShadowView tree mirrors RCT view tree. Every node is highly stateful.
* 1. A node is in one of three lifecycles: uninitialized, computed, dirtied.
* 1. RCTBridge may call any of the padding/margin/width/height/top/left setters. A setter would dirty
* the node and all of its ancestors.
* 2. At the end of each Bridge transaction, we call collectUpdatedFrames:widthConstraint:heightConstraint
* at the root node to recursively lay out the entire hierarchy.
* 3. If a node is "computed" and the constraint passed from above is identical to the constraint used to
* perform the last computation, we skip laying out the subtree entirely.
*/
然后將RCTContentRootView添加到RCTRootView上面,然后執(zhí)行了一行JS代碼逛万,告訴JS你要開始繪制這個(gè)參數(shù)params的界面
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _appProperties ?: @{},
};
RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}
再往后就是React.JS的工作了泳猬,React.JS會(huì)著手把JS中的頁(yè)面進(jìn)行計(jì)算,排版宇植,生成對(duì)應(yīng)的JS Component得封,準(zhǔn)備組織繪制界面了,包含著無(wú)數(shù)個(gè)JS Component的相互嵌套指郁。最終通過UIManager.js接口忙上,開始call oc去創(chuàng)建界面。
js 中如何處理 tag 的呢闲坎?
會(huì)跳過哪些 native 保留的 tag疫粥,也就是對(duì)10取余為1的那些 tag茬斧。然后自增。
/**
* Keeps track of allocating and associating native "tags" which are numeric,
* unique view IDs. All the native tags are negative numbers, to avoid
* collisions, but in the JS we keep track of them as positive integers to store
* them effectively in Arrays. So we must refer to them as "inverses" of the
* native tags (that are * normally negative).
*
* It *must* be the case that every `rootNodeID` always maps to the exact same
* `tag` forever. The easiest way to accomplish this is to never delete
* anything from this table.
* Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to
* unmount a component with a `rootNodeID`, then mount a new one in its place,
*/
var INITIAL_TAG_COUNT = 1;
var ReactNativeTagHandles = {
tagsStartAt: INITIAL_TAG_COUNT,
tagCount: INITIAL_TAG_COUNT,
allocateTag: function(): number {
// Skip over root IDs as those are reserved for native
while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
ReactNativeTagHandles.tagCount++;
}
var tag = ReactNativeTagHandles.tagCount;
ReactNativeTagHandles.tagCount++;
return tag;
},
assertRootTag: function(tag: number): void {
invariant(
this.reactTagIsNativeTopRootID(tag),
'Expect a native root tag, instead got %s',
tag,
);
},
reactTagIsNativeTopRootID: function(reactTag: number): boolean {
// We reserve all tags that are 1 mod 10 for native root views
return reactTag % 10 === 1;
},
};
實(shí)際跑一下梗逮,創(chuàng)建的第一個(gè) RootView
self->_shadowViewRegistry:
{
17 = "<RCTShadowView: 0x121ed46e0; viewName: RCTView; reactTag: 17; frame: {{0, 0}, {0, 0}}>";
15 = "<RCTShadowText: 0x123940ff0; viewName: RCTText; reactTag: 15; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}; text: Tap me to load the next scene>";
13 = "<RCTShadowView: 0x123940690; viewName: RCTView; reactTag: 13; frame: {{77.666666666666671, 357.66666666666669}, {258.66666666666669, 37.666666666666664}}>";
9 = "<RCTShadowText: 0x12393f670; viewName: RCTText; reactTag: 9; frame: {{0, 0}, {132.33333333333334, 17}}; text: Current Scene: haha>";
7 = "<RCTShadowView: 0x12393f2b0; viewName: RCTView; reactTag: 7; frame: {{0, 0}, {414, 736}}>";
5 = "<RCTShadowView: 0x12393cf60; viewName: RCTNavigator; reactTag: 5; frame: {{0, 0}, {414, 736}}>";
3 = "<RCTShadowView: 0x121e17f30; viewName: RCTView; reactTag: 3; frame: {{0, 0}, {414, 736}}>";
1 = "<RCTRootShadowView: 0x121ec5cf0; viewName: RCTRootContentView; reactTag: 1; frame: {{0, 0}, {414, 736}}>";
16 = "<RCTShadowRawText: 0x121ed3ee0; viewName: RCTRawText; reactTag: 16; frame: {{0, 0}, {nan, nan}}; text: Tap me to load the next scene>";
14 = "<RCTShadowView: 0x123940eb0; viewName: RCTView; reactTag: 14; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}>";
12 = "<RCTShadowRawText: 0x12393fc00; viewName: RCTRawText; reactTag: 12; frame: {{0, 0}, {nan, nan}}; text: haha>";
10 = "<RCTShadowRawText: 0x12393f8b0; viewName: RCTRawText; reactTag: 10; frame: {{0, 0}, {nan, nan}}; text: Current Scene: >";
8 = "<RCTShadowView: 0x121ecf2d0; viewName: RCTView; reactTag: 8; frame: {{141, 340.66666666666669}, {132.33333333333334, 17}}>";
6 = "<RCTShadowView: 0x121ec73b0; viewName: RCTNavItem; reactTag: 6; frame: {{0, 0}, {414, 736}}>";
4 = "<RCTShadowView: 0x12393ce20; viewName: RCTView; reactTag: 4; frame: {{0, 0}, {414, 736}}>";
2 = "<RCTShadowView: 0x12393cce0; viewName: RCTView; reactTag: 2; frame: {{0, 0}, {414, 736}}>";
}
創(chuàng)建了多個(gè) RootView 以后
{
410 = "<RCTShadowRawText: 0x1014d0af0; viewName: RCTRawText; reactTag: 410; frame: {{0, 0}, {nan, nan}}; text: Tap me to load the next scene>";
399 = "<RCTShadowView: 0x1016812f0; viewName: RCTNavigator; reactTag: 399; frame: {{0, 0}, {414, 736}}>";
407 = "<RCTShadowView: 0x10169bc10; viewName: RCTView; reactTag: 407; frame: {{77.666666666666671, 357.66666666666669}, {258.66666666666669, 37.666666666666664}}>";
396 = "<RCTShadowView: 0x101425af0; viewName: RCTView; reactTag: 396; frame: {{0, 0}, {414, 736}}>";
221 = "<RCTRootShadowView: 0x101487710; viewName: RCTRootContentView; reactTag: 221; frame: {{0, 0}, {414, 736}}>";
404 = "<RCTShadowText: 0x1016281d0; viewName: RCTText; reactTag: 404; frame: {{0, 0}, {132.33333333333334, 17}}; text: Current Scene: haha>";
412 = "<RCTShadowView: 0x101405e40; viewName: RCTView; reactTag: 412; frame: {{0, 0}, {0, 0}}>";
409 = "<RCTShadowText: 0x10148cf10; viewName: RCTText; reactTag: 409; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}; text: Tap me to load the next scene>";
398 = "<RCTShadowView: 0x1014069b0; viewName: RCTView; reactTag: 398; frame: {{0, 0}, {414, 736}}>";
406 = "<RCTShadowRawText: 0x10169c840; viewName: RCTRawText; reactTag: 406; frame: {{0, 0}, {nan, nan}}; text: haha>";
403 = "<RCTShadowView: 0x101441830; viewName: RCTView; reactTag: 403; frame: {{141, 340.66666666666669}, {132.33333333333334, 17}}>";
400 = "<RCTShadowView: 0x101677f00; viewName: RCTNavItem; reactTag: 400; frame: {{0, 0}, {414, 736}}>";
408 = "<RCTShadowView: 0x1014c9b60; viewName: RCTView; reactTag: 408; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}>";
397 = "<RCTShadowView: 0x10146c180; viewName: RCTView; reactTag: 397; frame: {{0, 0}, {414, 736}}>";
405 = "<RCTShadowRawText: 0x1016820d0; viewName: RCTRawText; reactTag: 405; frame: {{0, 0}, {nan, nan}}; text: Current Scene: >";
402 = "<RCTShadowView: 0x10e41c750; viewName: RCTView; reactTag: 402; frame: {{0, 0}, {414, 736}}>";
}
那么RCTUIManager都有哪些API提供給了JS呢项秉,大致如下:
createView
updateView
setChildren
removeRootView
manageChildren
findSubviewIn
measure
dispatchViewManagerCommand
createView的作用是創(chuàng)建一個(gè)個(gè)的UIView,RCTView库糠,各種nativeView伙狐,并且把傳過來(lái)的JS的屬性參數(shù),一一賦值給nativeView
updateView的作用是瞬欧,當(dāng)JSComponent的布局信息贷屎,界面樣子發(fā)生變化,JS來(lái)通知nativeView來(lái)更新對(duì)應(yīng)的屬性變化艘虎,樣子變化
setChildren的作用是唉侄,告訴OC,那個(gè)tag的View是另一個(gè)tag的view的子view野建,需要執(zhí)行addsubview属划,insertsubview等
如果要?jiǎng)?chuàng)建一個(gè) view,會(huì)經(jīng)過以下流程:
js傳來(lái)viewName候生,通過初始化的_componentDataByName表獲取RCTComponentData
dispatch_async(mainqueue)從JS通信線程拋到主線程創(chuàng)建UI
js傳來(lái)了ReactTag同眯,通過RCTComponentData的createViewWithTag方法創(chuàng)建界面
js傳來(lái)了屬性props,通過RCTComponentData的setProps:forView:方法進(jìn)行屬性賦值
mountComponent: function mountComponent(transaction, hostParent, hostContainerInfo, context) {
var tag = ReactNativeTagHandles.allocateTag();
this._rootNodeID = tag;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var updatePayload = ReactNativeAttributePayload.create(this._currentElement.props, this.viewConfig.validAttributes);
var nativeTopRootTag = hostContainerInfo._tag;
UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);
ReactNativeComponentTree.precacheNode(this, tag);
this.initializeChildren(this._currentElement.props.children, tag, transaction, context);
return tag;
}
這樣一來(lái)唯鸭,基本上就完成了React.JS創(chuàng)建一個(gè)純native界面的過程: