APP中RN頁面渲染流程-ReactNative源碼分析
在APP啟動后,RN框架開始啟動。等RN框架啟動后,就開始進行RN頁面渲染了。
RN頁面原生側頁面渲染的主要邏輯實現是在RCTUIManager和RCTShadowView完成的。
通過看UIMananger的源碼可以看到,UIMananger導出給JS端的API接口在對UI的操作上,基本都會同時對 View 和 ShadowView 進行操作。
以更新視圖為例:

RCTUIManager的作用
RCTUIManager的主要作用是負責管理React Native應用程序的視圖的創建、更新和銷毀。
具體為RCTUIManager負責創建并管理應用程序中的所有視圖,包括文本、圖像、按鈕等。當需要更新或銷毀視圖時,RCTUIManager會負責處理相應的操作。
RCTShadowView的作用
RCTShadowView的主要作用是在APP中創建一棵YaGoNode節點樹,用于記錄視圖的樣式、布局、事件響應等信息,用于描述真實視圖的屬性和布局,從而提高渲染性能和效率。,它和UIView的關系類似于前端的虛擬DOM樹和DOM樹,兩者是一一對應的關系。js對View的操作會先更新虛擬DOM,然后ReactNative在合適的時機批量更新到真實的View上。
RN頁面的創建
在創建自定義RCTView供js使用時,一般在創建一個RCTView時,都要創建一個對應的RCTViewManager用于管理Native與js的通訊。
UIMananger的創建在RN框架啟動時,它在創建時會通過RCT_EXPORT_METHOD()宏將操作view的添加,修改,刪除,調整層級等方法注入給js,供js操作原生view
RN框架啟動完成后則會進行RN頁面渲染。
首先,js引擎執行rn代碼,將rn中的組件轉換成原生view展示到頁面上。
js引擎執行js代碼, 它會根據RN頁面中的標簽類型轉換成一棵虛擬DOM樹,它們有原子組件,文本組件,組合組件組成。在這些組件的內部記錄著create視圖需要的參數。等要生成真實的視圖時,就會調用指令生成對應的視圖。

原生側createView方法的主要執行步驟為:
RCT_EXPORT_METHOD(createView : (nonnull NSNumber *)reactTag viewName : (NSString *)viewName rootTag : (nonnull NSNumber *)rootTag props : (NSDictionary *)props)
1.根據模塊名viewName從RCTBridge保存的全局變量中找到對應的模塊信息
2.根據模塊信息創建shadowview虛擬dom,保存到shadowView全局容器中
3.根據模塊信息在主線程創建原生view,保存到view全局容器中
然后,執行setChildren:設置子視圖
執行setChildren:設置子視圖, 會將view添加到容器view的reactSubviews中(shadowView和UIView都是放到對應容器的reactSubviews屬性中)[container insertReactSubview:view atIndex:index++];

原生側setChildren方法的主要執行步驟為:
RCT_EXPORT_METHOD(setChildren : (nonnull NSNumber *)containerTag reactTags : (NSArray<NSNumber *> *)reactTags)
1.設置shadowView子視圖,shadowView是設置到yoga樹的葉子節點中:YGNodeInsertChild(_yogaNode, subview.yogaNode, (uint32_t)atIndex);
2.把設置view子視圖任務添加到任務隊列,[_pendingUIBlocks addObject:block];隊列中的任務并不會立刻執行,而是等到合適的時機再執行。而當這個任務執行后,子View也并沒有到真實的subviews中,而是放置到了reactSubviews關聯屬性中 objc_setAssociatedObject(self, @selector(reactSubviews), subviews, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
_pendingUIBlocks隊列執行時機
_pendingUIBlocks隊列的刷新時機主要有2種,一種是在JS代碼執行完成,JS通過RCTCxxBridge調用batchDidComplete觸發刷新。另一種在原生APP中根據RN頁面的布局手動調用setNeedsLayout進行觸發。
JS通過RCTCxxBridge調用
在js執行期間,js引擎通過Bridge橋接,把涉及到UI操作的事件按順序封裝成UIBlock放到Native原生側的_pendingUIBlocks中,在等js代碼執行完成后,原生模塊會觸發一個UIManager.batchDidComplete事件,表示js批量任務執行完成,開始刷新_pendingUIBlocks隊列中的UI任務了。因此,在 JavaScript 執行完成前,RN 頁面的 UI 并不會立即刷新。
方法調用順序:batchDidComplete -> _layoutAndMount -> flushUIBlocksWithCompletion。
_pendingUIBlocks中的UIBlock執行后,最終會生成真實的原生view
struct RCTInstanceCallback : public InstanceCallback { __weak RCTCxxBridge *bridge_; RCTInstanceCallback(RCTCxxBridge *bridge) : bridge_(bridge){}; void onBatchComplete() override { // There's no interface to call this per partial batch [bridge_ partialBatchDidFlush]; [bridge_ batchDidComplete]; } };
RN頁面根據頁面布局手動調用
- (void)setSize:(CGSize)size forView:(UIView *)view { RCTAssertMainQueue(); [self _executeBlockWithShadowView:^(RCTShadowView *shadowView) { if (CGSizeEqualToSize(size, shadowView.size)) { return; } shadowView.size = size; [self setNeedsLayout]; } forTag:view.reactTag]; }
- (void)setNeedsLayout { // If there is an active batch layout will happen when batch finished, so we will wait for that. // Otherwise we immediately trigger layout. if (![_bridge isBatchActive] && ![_bridge isLoading]) { [self _layoutAndMount]; } }
RN頁面更新
當組件調用了setState屬性更新時,通過updateView:刷新視圖。
當出現插入、刪除、排序組件時,通過manageChildren:更新視圖。
updateView:刷新視圖
當在RN中通過setState更改屬性,js會對應生成一個新的虛擬DOM,通過diff算法,對應新舊DOM樹生成修改點,然后通過updateView事件,將屬性更新更新到原生側的shadowView和View的_UIPendingQueue中。

當出現插入、刪除、排序組件時,通過manageChildren:更新視圖
containerTag:表示容器組件的標識符,即將在其中管理子組件。
moveFromIndices和moveToIndices:表示要移動的子組件的原始位置和目標位置的索引。
addChildReactTags和addAtIndices:表示要添加的子組件的標識符和它們在父容器中的位置索引。
removeAtIndices:表示要從父容器中刪除的子組件的位置索引。
registry:表示React組件的注冊表,其中包含所有已注冊的組件及其實例。
