APP中RN頁面渲染流程-ReactNative原始碼分析

2023-05-25 06:01:02
在APP啟動後,RN框架開始啟動。等RN框架啟動後,就開始進行RN頁面渲染了。
RN頁面原生側頁面渲染的主要邏輯實現是在RCTUIManager和RCTShadowView完成的。
通過看UIMananger的原始碼可以看到,UIMananger匯出給JS端的API介面在對UI的操作上,基本都會同時對 View 和 ShadowView 進行操作。
以更新檢視為例:


RCTUIManager的作用
RCTUIManager的主要作用是負責管理React Native應用程式的檢視的建立、更新和銷燬;將原生元件註冊到JS端;處理原生和React Native之間的通訊;
具體如下:
1.檢視的建立、更新和銷燬。RCTUIManager負責建立並管理應用程式中的所有檢視,包括文字、影象、按鈕等。當需要更新或銷燬檢視時,RCTUIManager會負責處理相應的操作。
2.元件的註冊。RCTUIManager負責註冊應用程式中的所有元件,並提供相應的方法以便在React Native應用程式中使用。
3.原生和React Native之間的通訊。RCTUIManager通過橋接層(Bridge)與原生平臺進行通訊,使得React Native應用程式可以在原生平臺上執行和呈現UI介面。當React Native應用程式需要與原生平臺進行互動時,RCTUIManager會負責處理相應的操作。
 
RCTShadowView的作用 
RCTShadowView的主要作用是在APP中建立一棵YaGoNode節點樹,用於記錄檢視的樣式、佈局、事件響應等資訊,用於描述真實檢視的屬性和佈局,從而提高渲染效能和效率。,它和UIView的關係類似於前端的虛擬DOM樹和DOM樹,兩者是一一對應的關係。js對View的操作會先更新虛擬DOM,然後ReactNative在合適的時機批次更新到真實的View上。

RN頁面的建立
在建立自定義RNView供js使用時,一般在建立一個RNView時,都要建立一個對應的RNViewManager用於管理Native與js的通訊。
UIMananger的建立在RN框架啟動時,它在建立時會通過RCT_EXPORT_METHOD()宏將操作view的新增,修改,刪除,調整層級等方法注入給js,供js操作原生view
RN框架啟動完成後則會進行RN頁面渲染。
 
首先,js引擎執行rn程式碼,將rn中的元件轉換成原生view展示到頁面上。
js通過執行create程式碼,建立原生View

原生側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佇列執行時機
在js執行期間,js引擎通過Bridge橋接,把涉及到UI操作的事件按順序封裝成UIBlock放到Native原生側的_pendingUIBlocks中,在等js程式碼執行完成後,原生模組會觸發一個UIManager.batchDidComplete事件,表示js批次任務執行完成,開始重新整理uiPending佇列中的UI任務了。因此,在 JavaScript 執行完成前,RN 頁面的 UI 並不會立即重新整理。
方法呼叫順序:batchDidComplete -> _layoutAndMount -> flushUIBlocksWithCompletion。
_pendingUIBlocks中的UIBlock執行後,最終會生成真實的原生view
- (void)didUpdateReactSubviews
{
  for (UIView *subview in self.reactSubviews) {
    [self addSubview:subview];
  }
}
 
 
RN頁面更新
當元件呼叫了setState屬性更新時,通過updateView:重新整理檢視。
當出現插入、刪除、排序元件時,通過manageChildren:更新檢視。
updateView:重新整理檢視
當在RN中通過setState更改屬性,js會對應生成一個新的虛擬DOM,通過diff演演算法,對應新舊DOM樹生成修改點,然後通過updateView事件,將屬性更新更新到原生側的shadowView和View的_UIPendingQueue中。
 
當出現插入、刪除、排序元件時,通過manageChildren:更新檢視
containerTag:表示容器元件的識別符號,即將在其中管理子元件。
moveFromIndices和moveToIndices:表示要移動的子元件的原始位置和目標位置的索引。
addChildReactTags和addAtIndices:表示要新增的子元件的識別符號和它們在父容器中的位置索引。
removeAtIndices:表示要從父容器中刪除的子元件的位置索引。
registry:表示React元件的登入檔,其中包含所有已註冊的元件及其範例。