ReactNative源碼分析 - 渲染原理

1.ReactNative源碼分析 - 概述
2.ReactNative源碼分析 - JavaScriptCore C語言篇
3.ReactNative源碼分析 - 啟動流程
4.ReactNative源碼分析 - 通信機制
5.ReactNative源碼分析 - 渲染原理

  • 一、前言
  • 二、React簡介
  • 三、原生端與渲染流程相關(guān)的類
    • 1.原生端UI組件體系
    • 2.原生端UI管理者體系
  • 四、原生控件信息如何傳遞到JS端
    • 1.RCTViewManager定義導(dǎo)出屬性
    • 2.RCTUIManager收集原生控件信息
    • 3.UIManager獲取原生控件信息
    • 4.原生控件信息處理、獲取
  • 五、渲染流程
    • 1.注冊JS組件
    • 2.運行JS組件,執(zhí)行渲染
    • 3.獲取React計算結(jié)果
    • 4.驅(qū)動Native執(zhí)行渲染流程
  • 六、JS端調(diào)用原生控件導(dǎo)出函數(shù)
  • 七、結(jié)語

一、前言

  • 1.本文主要分析JS端業(yè)務(wù)層編寫的React組件最終如何渲染為原生控件。渲染原理的完整流程梳理起來有點困難,難在此處開始與React銜接,而React又是一個非常龐大的框架。本文對React不過多展開討論,首先筆者并不擅長React,沒有從源碼層面完整梳理過它的工作流程,再者這個流程分析起來又是一個系列的文章。本文以React最終產(chǎn)生的結(jié)果(組件信息JSON數(shù)據(jù))為基礎(chǔ),分析它如何驅(qū)動原生端進行UI繪制。
  • 2.ReactNative以React的模式開發(fā),最終通過底層Bridge驅(qū)動原生端進行相應(yīng)操作。從UI渲染原理層面分析,大致流程是:
    • 承載業(yè)務(wù)邏輯的React組件最終生成描述原生控件信息的JSON數(shù)據(jù),通過底層Bridge傳遞到原生端,驅(qū)動原模塊繪制原生UI控件;
    • 用戶行為觸發(fā)原生UI控件交互事件/回調(diào)事件從原生端傳遞到JS端以處理業(yè)務(wù)邏輯,業(yè)務(wù)邏輯處理結(jié)果通常表現(xiàn)為原生控件信息的更新,新的信息數(shù)據(jù)會再次傳遞到原生端,驅(qū)動原生模塊進行新一輪UI更新……
  • 3.本文很多邏輯是基于通信機制(eg:原生模塊信息導(dǎo)出流程、Native&JS通信),本文假設(shè)你已了解這些知識。

二、React簡介

  • JSX是JavaScript的語法擴展,用來書寫聲明式UI。最終會通過babel 轉(zhuǎn)碼,轉(zhuǎn)化常規(guī)JavaScript代碼。
import React from 'react';
import {View, TouchableOpacity} from 'react-native';

export default class TestRender extends React.Component {
    render() {
     return  (
        <View style={{ backgroundColor: 'white', paddingTop: 64,}} testID="white">
            <View style={{ backgroundColor: "yellow",width: 50, height: 50}} testID="white-yellow"/>
            <View 
                style={{backgroundColor: 'green',width: 100, height: 100,justifyContent:"center",alignItems:"center"}} testID="white-green"
            >
                <View style={{backgroundColor: 'red', width: 50, height: 50,}} testID="white-green-red"></View>
            </View>
            <TouchableOpacity
                style={{backgroundColor: 'blue', width: 50, height: 50}}
                testID="white-blue"
                onPress={() => console.log("onPress") }
            />
        </View>
    );
    }
}

上述JSX轉(zhuǎn)碼后變成如下代碼。每個React元素轉(zhuǎn)化為React.createElement函數(shù)調(diào)用,最終返回一個描述組件信息的JS對象,其中嵌套關(guān)系表示父子組件關(guān)系。

export default class TestRender extends React.Component {
    render() {
      return 
        React.createElement(View, {
            style: {
                backgroundColor: 'white',
                paddingTop: 64
            },
            testID: "white"
        }, React.createElement(View, {
                style: {
                    backgroundColor: "yellow",
                    width: 50,
                    height: 50
                },
                testID: "white-yellow"
            }),
            React.createElement(View, {
                style: {
                    backgroundColor: 'green',
                    width: 100,
                    height: 100,
                    justifyContent: "center",
                    alignItems: "center"
                },
                testID: "white-green"
            }, React.createElement(View, {
                    style: {
                        backgroundColor: 'red',
                        width: 50,
                        height: 50
                    },
                    testID: "white-green-red"
                })), 
            React.createElement(TouchableOpacity, {
                style: {
                    backgroundColor: 'blue',
                    width: 50,
                    height: 50
                },
                testID: "white-blue",
                onPress: () => console.log("onPress")})
        );
    }
  }
  • React Element即React元素是React應(yīng)用的最小單元。
const element = <h1>Hello, world</h1>;
  • ReactComponent即React組件是獨立、可復(fù)用的UI模塊,它可同時包含UI渲染、業(yè)務(wù)邏輯。組件是由元素構(gòu)成的,React應(yīng)用由一系列的React組件組合起來的。
    關(guān)于React的源碼分析,可以參考React源碼解析(一):組件的實現(xiàn)與掛載,結(jié)合React源碼分析React.createElement最終返回JS對象的過程。

三、原生端與渲染流程相關(guān)的類

1.原生端UI組件體系

View體系.jpg

原生端與UI渲染相關(guān)的視圖類體系如圖所示,ReactNative封裝了一系列原生控件,把控件信息導(dǎo)出到JS端并且包裝為對應(yīng)的組件供JS業(yè)務(wù)層使用,我們暫且把前者稱為控件,后者稱為組件,兩者在Native端、JS端一一對應(yīng)。

  • RCTComponent協(xié)議定義了一套標(biāo)準(zhǔn)接口,作為視圖樹的邏輯結(jié)點。RCTShadowViewUIView遵守該協(xié)議,使得對這兩者組成的樹View Tree、ShadowView Tree的操作更統(tǒng)一。
  • ReactNative封裝的原生控件(RCTView、RCTTextView、RCTImageView……)最終都繼承自UIView,即默認實現(xiàn)了RCTComponent協(xié)議;
    RCTShadowView可理解為布局結(jié)點,js端傳遞到原生端的控件信息,與布局相關(guān)的會賦值給它,并通過它驅(qū)動yoga計算出組件的布局信息(frame、center);
    總的來說:在ReactNative渲染過程中,ShadowView與UIView是對應(yīng)關(guān)系,每個UIView都有一個與之對應(yīng)的ShadowView。UIView是最終渲染出來的視圖、ShadowView是它的布局結(jié)點,負責(zé)為它計算布局信息。渲染過程中存在兩棵樹:控件樹、布局結(jié)點樹,兩者的結(jié)點一一對應(yīng),互為鏡子。
  • RCTRootView根視圖是ReactNative暴露給原生端使用的控件,可以像普通UIView一樣使用。主要負責(zé)運行js端通過AppRegistry.registerComponent注冊的根組件。混合開發(fā)應(yīng)用中通常是存在多個RCTRootView,共用同一個底層Bridge。
  • RCTRootContentView作為RCTRootView的子視圖,RCTRootView只是負責(zé)運行JS組件,后續(xù)JS端驅(qū)動創(chuàng)建的控件繪制到RootContentView上。
  • RCTRootContentViewRCTRootShadowView是對應(yīng)關(guān)系。

2.原生端UI管理者體系

Manager體系.jpg

原生端與UI渲染相關(guān)的管理者類體系如圖所示

  • RCTUIManager、RCTViewManager都是原生模塊,由于懶加載機制他們實際上是單例,最終都通過底層Bridge把模塊信息導(dǎo)出到JS端使用。

    • RCTViewManager與導(dǎo)出控件一一對應(yīng),負責(zé)管理對應(yīng)的原生控件,包括定義要導(dǎo)出的控件屬性、接口;接收通過RCT_CUSTOM_VIEW_PROPERTY導(dǎo)出的屬性賦值;創(chuàng)建控件、創(chuàng)建布局結(jié)點……
    • RCTUIManager是渲染流程中的集大成者,它接收JS端發(fā)送過來的指令,執(zhí)行UI控件創(chuàng)建/移除、屬性設(shè)置/更新、動畫、調(diào)用原生控件導(dǎo)出函數(shù)等。
      RCTViewManager創(chuàng)建原生控件并提供給RCTUIManager,RCTUIManager則會反過來委托它們在需要的時候去設(shè)置和更新視圖的屬性,甚至調(diào)用控件導(dǎo)出函數(shù)。
  • RCTComponentData包裝每一個RCTViewManager,RCTUIManager通過它來:

    • 1.創(chuàng)建原生控件、布局結(jié)點;
    • 2.設(shè)置控件屬性、布局屬性;
    • 3.獲取控件導(dǎo)出屬性集合。

四、原生控件信息如何傳遞到JS端

這里以RCTWKWebView為例分析原生控件信息導(dǎo)出流程,理解RCTWKWebView的封裝,你一定可以封裝自定義原生控件供JS端使用

1.RCTViewManager定義導(dǎo)出屬性

RCTViewManager控件屬性導(dǎo)出宏生成的對應(yīng)函數(shù),用于后續(xù)獲取導(dǎo)出屬性信息

  • RCT_EXPORT_VIEW_PROPERTY生成屬性名、屬性類型獲取函數(shù)。后續(xù)通過Runtime獲取以propConfig_為前綴的函數(shù)并調(diào)用以獲取這些信息。
  • RCT_EXPORT_SHADOW_PROPERTY生成布局屬性名、屬性類型獲取函數(shù)。后序通過Runtime獲取以propConfigShadow_為前綴的函數(shù)并調(diào)用以獲取這些信息。
  • RCT_CUSTOM_VIEW_PROPERTY生成屬性名、屬性類型獲取函數(shù),并生成屬性setter
// RCTWKWebViewManager.m
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RCTWKWebView) {
  view.bounces = json == nil ? true : [RCTConvert BOOL: json];
}
// 生成屬性type keypath獲取函數(shù)
+ (NSArray<NSString *> *)propConfig_source { 
    return @[@"NSDictionary"]; 
}
+ (NSArray<NSString *> *)propConfig_onLoadingStart { 
    return @[@"RCTDirectEventBlock"]; 
}

+ (NSArray<NSString *> *)propConfig_bounces { 
    return @[@"BOOL", @"__custom__"]; 
} 
- (void)set_bounces:(id)json forView:(RCTWKWebView *)view withDefaultView:(RCTWKWebView *)defaultView {
    view.bounces = json == ((void *)0) ? 1 : [RCTConvert BOOL: json];
}
// RCTViewManager.m
RCT_EXPORT_SHADOW_PROPERTY(marginTop, YGValue)
+ (NSArray<NSString *> *)propConfigShadow_marginTop { 
    return @[@"YGValue"]; 
}

2.RCTUIManager收集原生控件信息

  • RCTUIManager實例化后執(zhí)行setBridge:,會收集所有的原生控件管理者RCTViewManager,并創(chuàng)建對應(yīng)的RCTComponentData
// RCTUIManager.m
- (void)setBridge:(RCTBridge *)bridge
{
  _bridge = bridge;
  ...
  // 收集RCTViewManager
  _componentDataByName = [NSMutableDictionary new];
  for (Class moduleClass in _bridge.moduleClasses) {
    if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
      RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:_bridge];
      _componentDataByName[componentData.name] = componentData;
    }
  }
}
  • RCTComponentData包裝了RCTViewManager,因此它可獲取原生控件的一切導(dǎo)出信息。
    RCTViewManager通過宏導(dǎo)導(dǎo)出屬性/布局屬性,分別生成的導(dǎo)出函數(shù)前綴propConfig_propConfigShadow_
    函數(shù)viewConfig使用Runtime遍歷ViewManager所有類函數(shù)以獲取導(dǎo)出屬性信息,收集以propConfig開頭的函數(shù),截取函數(shù)名字符串_后面的內(nèi)容獲取屬性名;調(diào)用函數(shù)獲取屬性類型。
// RCTComponentData.m

// 獲取控件導(dǎo)出屬性
- (NSDictionary<NSString *, id> *)viewConfig
{
  NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
  NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
  
  // Runtime獲取屬性/布局屬性導(dǎo)出宏生成的函數(shù),函數(shù)前綴`propConfig`
  unsigned int count = 0;
  NSMutableDictionary *propTypes = [NSMutableDictionary new];
  Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
  for (unsigned int i = 0; i < count; i++) {
    SEL selector = method_getName(methods[i]);
    const char *selectorName = sel_getName(selector);
    
    // 過濾無propConfig前綴的函數(shù)
    if (strncmp(selectorName, "propConfig", strlen("propConfig")) != 0) {
      continue;
    }

    // 控件導(dǎo)出的屬性/布局屬性生成的函數(shù)分別是propConfig_* propConfigShadow_* ,定位'_'的位置,進而獲取屬性名
    const char *underscorePos = strchr(selectorName + strlen("propConfig"), '_');
    if (!underscorePos) {
      continue;
    }
    NSString *name = @(underscorePos + 1);
    
    // 調(diào)用函數(shù)獲取屬性類型
    NSString *type = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0];
    
    // 回調(diào)類型屬性,用BOOL代替
    if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
      [bubblingEvents addObject:RCTNormalizeInputEventName(name)];
      propTypes[name] = @"BOOL";
    } else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
      [directEvents addObject:RCTNormalizeInputEventName(name)];
      propTypes[name] = @"BOOL";
    } else {
      propTypes[name] = type;
    }
  }
  free(methods);
  
  Class superClass = [_managerClass superclass];
  
  return @{
    @"propTypes": propTypes,
    @"directEvents": directEvents,
    @"bubblingEvents": bubblingEvents,
    @"baseModuleName": superClass == [NSObject class] ? (id)kCFNull : moduleNameForClass(superClass),
  };
}

例子RCTWKWebViewManager返回結(jié)果如下
propTypes存放所有導(dǎo)出屬性的屬性名、類型;
directEvents、bubblingEvents存放回調(diào)類型;
baseModuleName表示控件基類,用于在JS端添加基類導(dǎo)出屬性以得到完整屬性表。

{
     "propTypes": {
        "allowsInlineMediaPlayback": "BOOL",
        "automaticallyAdjustContentInsets":"BOOL",
        "bounces" : "BOOL",
        "contentInset": "UIEdgeInsets",
        ...
        "onMessage":""BOOL,
        "onShouldStartLoadWithRequest":"BOOL"
     },
     "directEvents": [
        "topLoadingStart",
        "topLoadingFinish",
        "topLoadingError",
        "topMessage",
        "topShouldStartLoadWithRequest"
     ],
     "bubblingEvents": [],
     "baseModuleName": "RCTViewManager"
   }
  • RCTUIManager原生模塊導(dǎo)出常量constantsToExport用來導(dǎo)出所有原生控件屬性信息。
    這里遍歷所有原生控件以收集屬性信息,最終加工為一個包含所有原生控件屬性信息的集合,導(dǎo)出到JS端。
// 收集所有導(dǎo)出供JS端使用的原生控件的信息
- (NSDictionary<NSString *, id> *)constantsToExport
{
  return [self getConstants];
}

- (NSDictionary<NSString *, id> *)getConstants
{
  NSMutableDictionary<NSString *, NSDictionary *> *constants = [NSMutableDictionary new];
  NSMutableDictionary<NSString *, NSDictionary *> *directEvents = [NSMutableDictionary new];
  NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents = [NSMutableDictionary new];
  
  [_componentDataByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
    NSMutableDictionary<NSString *, id> *moduleConstants = moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
    constants[name] = moduleConstants;
  }];
  return constants;
}

static NSMutableDictionary<NSString *, id> *moduleConstantsForComponent(
    NSMutableDictionary<NSString *, NSDictionary *> *directEvents,
    NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents,
    RCTComponentData *componentData) {
  NSMutableDictionary<NSString *, id> *moduleConstants = [NSMutableDictionary new];
 
  NSMutableDictionary<NSString *, NSDictionary *> *bubblingEventTypes = [NSMutableDictionary new];
  NSMutableDictionary<NSString *, NSDictionary *> *directEventTypes = [NSMutableDictionary new];

  moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);

  NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
  moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
  moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
  moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes;
  moduleConstants[@"directEventTypes"] = directEventTypes;

  for (NSString *eventName in viewConfig[@"directEvents"]) {
    if (!directEvents[eventName]) {
      directEvents[eventName] = @{
                                  @"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
                                  };
    }
    directEventTypes[eventName] = directEvents[eventName];
  }

  // Add bubbling events
  for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
    if (!bubblingEvents[eventName]) {
      NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
      bubblingEvents[eventName] = @{
                                    @"phasedRegistrationNames": @{
                                        @"bubbled": bubbleName,
                                        @"captured": [bubbleName stringByAppendingString:@"Capture"],
                                        }
                                    };
    }
    bubblingEventTypes[eventName] = bubblingEvents[eventName];
  }

  return moduleConstants;
}

最終生成一張表包含所有原生控件導(dǎo)出屬性信息<原生控件名:導(dǎo)出信息>,格式如下。作為RCTUIManager原生模塊常量導(dǎo)出到JS端,原生模塊信息導(dǎo)出流程詳解通信機制

{
   "RCTWKWebView": {
     "Manager": "WKWebViewManager",
     "NativeProps" : {
         "allowsInlineMediaPlayback" :"BOOL",
         ...
         "onLoadingError" : "BOOL",
         "onLoadingFinish" : "",
         "onLoadingStart" : "BOOL",
         "onMessage" : "BOOL",
         "source" : "NSDictionary",
      },
      "baseModuleName" : "RCTView",
      "bubblingEventTypes" : { };
      "directEventTypes" {
         "topLoadingError" : { "registrationName" : "onLoadingError",},
         "topLoadingFinish" : { "registrationName" : "onLoadingFinish",},
         ...
       },
    },
   
   "RCTView": {...},
   
   ...
   }

3.UIManager獲取原生控件信息

  • 注:
    通常情況下:ReactNative原生模塊(特別是邏輯模塊)在JS端會包裝為對應(yīng)的JS模塊,例如RCTUIManager在JS端包裝為UIManager。 這樣做有諸多好處,首先Native、JS端一一對應(yīng),其次是更清晰的模塊化,還是可以緩存原生模塊信息。

原生控件管理者ViewManager通常不會包裝,原因是原生模塊信息幾乎都通過UIManager獲取,不會直接使用。但原生控件信息導(dǎo)出到JS端,會包裝為對應(yīng)組件,例如RCTWKWebView在JS端會包裝為WebView.ios.js,這么做的好處同上。

  • UIManager實際上就做一件事情:加載所有原生控件信息并緩存,供調(diào)用者使用。
    上文分析過RCTUIManager導(dǎo)出常量是所有原生控件信息,根據(jù)通信機制:原生模塊信息如何導(dǎo)入到JS端,可知相應(yīng)的JS端模塊UIManager能獲取到這些原生控件信息。UIManager.js腳本運行時,會遍歷UIManager模塊信息,找出原生控件信息(判斷是否有Manager屬性),并且進行加工、緩存。
    加工的過程很簡單:獲取相應(yīng)ViewManager并把導(dǎo)出函數(shù)getter、常量getter添加到原生控件信息中。至此,JS端緩存的原生控件信息相對完整,包含原生控件的導(dǎo)出屬性、函數(shù)、常量、管理者ViewManager、基類。
    UIManager把原生控件信息都命名為ViewManagerConfig,即原生控件管理者信息,其實是就是原生控件信息。
// UIManager.js

const {UIManager} = NativeModules;
// 存放所有原生控件信息 { viewName: viewConfig }
const viewManagerConfigs = {};

// 獲取控件信息
UIManager.getViewManagerConfig = function(viewManagerName: string) {
  const config = viewManagerConfigs[viewManagerName];
  if (config) {
    return config;
  }

  // 防御作用,獲取不到viewConfig時,使用同步函數(shù)獲取試圖獲取
  if (UIManager.lazilyLoadView && !triedLoadingConfig.has(viewManagerName)) {
    ...
  }
  return viewManagerConfigs[viewManagerName];
};

// 原生控件配置信息viewConfig
function lazifyViewManagerConfig(viewName) {
  const viewConfig = UIManager[viewName];
  if (viewConfig.Manager) {
    // viewConfig存入組件信息表viewManagerConfigs
    viewManagerConfigs[viewName] = viewConfig;

    // 定義取值函數(shù)getter,用于獲取Constants,即ViewManager導(dǎo)出常量
    defineLazyObjectProperty(viewConfig, 'Constants', {
      get: () => {
        const viewManager = NativeModules[viewConfig.Manager];
        const constants = {};
        viewManager &&
          Object.keys(viewManager).forEach(key => {
            const value = viewManager[key];
            if (typeof value !== 'function') {
              constants[key] = value;
            }
          });
        return constants;
      },
    });
    
    // 定義取值函數(shù),用于獲取Commands,即ViewManager導(dǎo)出函數(shù)(索引)
    defineLazyObjectProperty(viewConfig, 'Commands', {
      get: () => {
        const viewManager = NativeModules[viewConfig.Manager];
        const commands = {};
        let index = 0;
        viewManager &&
          Object.keys(viewManager).forEach(key => {
            const value = viewManager[key];
            if (typeof value === 'function') {
              commands[key] = index++;
            }
          });
        return commands;
      },
    });
  }
}

// 遍歷UIManager模塊信息,獲取、加工組件信息,并緩存
if (Platform.OS === 'ios') {
  Object.keys(UIManager).forEach(viewName => {
    lazifyViewManagerConfig(viewName);
  });
}

// 導(dǎo)出UIManager對象,單例
module.exports = UIManager;

最終原生控件信息存放在viewManagerConfigs,格式如下

{
    RCTWKWebView: {
        Commands: {
             getConstants: 7,
             goBack: 1,
             goForward: 2,
             ...
        },
        Constants: {},
        Manager: "WKWebViewManager",
        NativeProps: {
            allowsInlineMediaPlayback: "BOOL",
            bounces: "BOOL",
            ...
            onMessage: "BOOL",
        },
        baseModuleName: "RCTView",
        bubblingEventTypes:{ },
        directEventTypes: {
            topLoadingStart: {registrationName: "onLoadingStart"},
            topMessage: {registrationName: "onMessage"},
            ...
         }
    },
    
    RCTView: {
        ...
    }
    
    ...
}

4.原生控件信息處理、獲取

上訴流程中,UIManager單例收集所有原生控件信息,但還不能直接使用,下面進一步分析信息的處理、使用流程。

原生控件信息傳遞.jpg
  • 1.原生控件在JS端封裝對應(yīng)JS組件,會執(zhí)行requireNativeComponent函數(shù),傳入原生端導(dǎo)入的原生組件名。
// WebView.ios.js
const RCTWKWebView = requireNativeComponent('RCTWKWebView');

該函數(shù)其實是createReactNativeComponentClass函數(shù)的一層包裝,傳入一個回調(diào)函數(shù)。

// requireNativeComponent.js
const requireNativeComponent = (uiViewClassName: string): string =>
    createReactNativeComponentClass(uiViewClassName, () =>
        getNativeComponentAttributes(uiViewClassName),
);

getNativeComponentAttributes,負責(zé)從UIManager獲取原生控件信息,并做進一步加工:添加基類屬性;添加屬性比較/處理函數(shù)。最終得到完整的原生控件信息。它作為回調(diào)函數(shù)存放到ReactNativeViewConfigRegistry中,以懶加載完整的原生控件信息。

// 獲取原生控件信息
// getNativeComponentAttributes.js 
function getNativeComponentAttributes(uiViewClassName: string) {
  // 從UIManager獲取原生控件信息
  const viewConfig = UIManager.getViewManagerConfig(uiViewClassName);

  // 添加基類原生模塊信息
  let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;
  let nativeProps = viewConfig.NativeProps;
  while (baseModuleName) {
    const baseModule = UIManager.getViewManagerConfig(baseModuleName);
    ...
  }

  // 添加屬性比較、處理函數(shù)
  const validAttributes = {};
  for (const key in nativeProps) {
    const typeName = nativeProps[key];
    const diff = getDifferForType(typeName);
    const process = getProcessorForType(typeName);
    validAttributes[key] =
      diff == null && process == null ? true : {diff, process};
  }

  Object.assign(viewConfig, {
    uiViewClassName,  
    validAttributes,  
    bubblingEventTypes,
    directEventTypes,
  });
  return viewConfig;
}
  • 2.createReactNativeComponentClass函數(shù),則調(diào)用ReactNativeViewConfigRegistry模塊的register函數(shù),注冊原生控件信息獲取回調(diào)。
// 注冊原生控件信息獲取回調(diào)
// createReactNativeComponentClass.js
const createReactNativeComponentClass = function(
  name: string,
  callback: ViewConfigGetter,
): string {
  return register(name, callback);
};

ReactNativeViewConfigRegistry模塊就是一個原生控件信息注冊機,它包含所有需要的原生控件信息,并且是懶加載機制。
調(diào)用register函數(shù)注冊原生控件信息獲取回調(diào),并原路返回傳入的view name,作為組件名書寫JSX。

// ReactNativeViewConfigRegistry.js

const viewConfigCallbacks = new Map(); // 原生控件信息獲取回調(diào)函數(shù)表
const viewConfigs = new Map();         // 原生控件信息表

// 注冊原生控件信息獲取回調(diào),用于從UIManager獲取組件信息
exports.register = function(name: string, callback: ViewConfigGetter): string {
  viewConfigCallbacks.set(name, callback);
  return name;
};

 // 獲取原生控件信息,懶加載機制
 exports.get = function(name: string): ReactNativeBaseComponentViewConfig<> {
  let viewConfig;
  if (!viewConfigs.has(name)) {
    const callback = viewConfigCallbacks.get(name);
    viewConfigCallbacks.set(name, null);
    viewConfig = callback();
    processEventTypes(viewConfig);
    viewConfigs.set(name, viewConfig);
  } else {
    viewConfig = viewConfigs.get(name);
  }
  return viewConfig;
};

總結(jié)
1.封裝組件時調(diào)用requireNativeComponent注冊一個原生控件信息獲取/處理回調(diào)到原生控件信息注冊機ReactNativeViewConfigRegistry
2.真正執(zhí)行渲染,使用到對應(yīng)組件,調(diào)用get函數(shù)從原生控件信息注冊機獲取原生控件信息,若控件信息存在,則直接使用;否則執(zhí)行原生控件信息獲取/處理回調(diào),以得到完整并原生控件信息并緩存。
這一流程驅(qū)使原生控件數(shù)據(jù)進行加工,從UIManager流行ReactNativeViewConfigRegistry,并且具備懶加載特性。

五、渲染流程

1.注冊JS組件

  • ReactNative項目會在JS執(zhí)行的入口文件,注冊根組件到AppRegistry
// index.js
AppRegistry.registerComponent('Main', () => Main);
  • AppRegistry:App注冊機,準(zhǔn)確來說是根組件注冊機,因為一個應(yīng)用中可存在多個根組件。AppRegistry主要負責(zé):根組件的注冊、運行、卸載。它注冊到BatchedBridge作為JS模塊,可供原生端調(diào)用。
    • 1.JS端通過registerComponent注冊根組件到runnables表中;
    • 2.原生端通過runApplication運行指定的根組件;
    • 3.原生端在組件銷毀時,通過unmountApplicationComponentAtRootTag卸載指定的根組件;
// AppRegistry.js

const runnables: Runnables = {};

const AppRegistry = {
  // 注冊組件
  registerComponent(appKey: string, componentProvider: ComponentProvider, section?: boolean): string {
    runnables[appKey] = {
      componentProvider,
      run: ...
    };
    return appKey;
  },

  // 運行組件:獲取注冊表中的組件,運行
  runApplication(appKey: string, appParameters: any): void {
    runnables[appKey].run(appParameters);
  },

  // 卸載組件
  unmountApplicationComponentAtRootTag(rootTag: number): void {
    ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag);
  },
}

// 注冊JS模塊 AppRegistry
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);

2.運行JS組件,執(zhí)行渲染

  • 原生端RCTRootView在js bundle執(zhí)行完畢后,運行指定的JS組件
// RCTRootView.m
- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag": _contentView.reactTag,
    @"initialProps": _appProperties ?: @{},
  };
  
  // 調(diào)用js模塊AppRegistry runApplication,運行組件
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}
  • 上述操作會執(zhí)行JS模塊AppRegistry運行根組件接口runApplication。根據(jù)組件名appKey獲取對應(yīng)的根組件,調(diào)用run運行根組件,最終執(zhí)行renderApplication進行渲染。
// AppRegistry.js

runnables[appKey] = {
 // 組件獲取函數(shù)
 componentProvider,
 // 組件運行函數(shù), 執(zhí)行render
 run: appParameters => {
   renderApplication(
     componentProviderInstrumentationHook(componentProvider), 
     appParameters.initialProps,  // 原生層傳遞過來的初始化屬性
     appParameters.rootTag,       // rootTag
     wrapperComponentProvider && wrapperComponentProvider(appParameters),
     appParameters.fabric,
   );
 },
};
  • renderApplication主要負責(zé)
    • 使用AppContainer包裝根組件。AppContainer主要是包含了YellowBox之類的調(diào)試組件;
    • 調(diào)用ReactNativerender函數(shù)進行渲染。
      ReactFabric應(yīng)該是React的正在重構(gòu)的下一代渲染機制,目前還沒有生效。
// renderApplication.js

function renderApplication<Props: Object>(
  RootComponent: React.ComponentType<Props>,  // 根組件
  initialProps: Props,
  rootTag: any,  
  WrapperComponent?: ?React.ComponentType<*>,
  fabric?: boolean,
  showFabricIndicator?: boolean,) 
{
  // 根組件RootComponent嵌入容器組件AppContainer(用于包裝yellowBox等調(diào)試組件)
  let renderable = (
    <AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
      <RootComponent {...initialProps} rootTag={rootTag} />
    </AppContainer>
  );

  if (fabric) {
    require('ReactFabric').render(renderable, rootTag);
  } else {
    // 執(zhí)行渲染
    require('ReactNative').render(renderable, rootTag);
  }
}
  • ReactNative根據(jù)運行環(huán)境執(zhí)行相應(yīng)腳本。調(diào)試環(huán)境使用ReactNativeRenderer-dev;生產(chǎn)環(huán)境使用ReactNativeRenderer-prod。這里分析調(diào)試環(huán)境。
// ReactNative.js

if (__DEV__) {
  ReactNative = require('ReactNativeRenderer-dev');
} else {
  ReactNative = require('ReactNativeRenderer-prod');
}
  • 再往下追蹤,發(fā)現(xiàn)上述兩個文件的代碼量都是萬行級的,這顯然不是人看的代碼。當(dāng)然筆者相信FaceBook工程師不會寫可讀性這么差的代碼。經(jīng)探索發(fā)現(xiàn)這兩個不是源文件,真正的源文件可在React中查看。
    React項目十分龐大,本文不作展開,我們只需知道它采用monorepo管理方式,一個項目拆分為多個獨立包的。React中應(yīng)用于ReactNative的包主要是如下紅框所示,負責(zé)渲染、事件系統(tǒng)、協(xié)調(diào)等,使得React與ReactNative能鏈接起來,詳見源碼概覽
    早在遠古時期react-native-0.45.0,React的源碼是直接引入ReactNative,這種應(yīng)該比較好梳理邏輯。畢竟在無法調(diào)試代碼的情況下研究源碼太考驗想象力了。
15754700576459.jpg

3.獲取React計算結(jié)果

React與ReactNative的銜接是個大工程,此處省略一萬字,直接分析兩者最終的計算結(jié)果如何驅(qū)動原生端執(zhí)行UI渲染。

Debug環(huán)境下,調(diào)用ReactNativeRenderer-dev.js導(dǎo)出對象ReactNativeRenderer的渲染函數(shù)render執(zhí)行渲染。經(jīng)過一個長長的調(diào)用棧之后會執(zhí)行根組件的render(可在根組件render打斷點追蹤調(diào)用棧),返回一個根React Element。這就是React計算結(jié)果了,即我們用React編寫的JSX代碼的最終產(chǎn)物。
DEMO中的例子TestRender,最終返回React Element對象如下(省略了部分信息,testID為測試id),這其實就是一棵樹,包含原生控件信息。

{
    $$typeof: Symbol(react.element),
    type: {$$typeof: Symbol(react.forward_ref), displayName: "View",},
    props: {
        children: [
            {
                $$typeof: Symbol(react.element),
                type: {$$typeof: Symbol(react.forward_ref), displayName: "View"},
                props: {
                    style: {backgroundColor: "yellow", width: 50, height: 50},
                    testID: "white-yellow"
                },
            },
            {
                $$typeof: Symbol(react.element),
                type: {$$typeof: Symbol(react.forward_ref), displayName: "View"},
                props:{
                    children: [{
                        $$typeof: Symbol(react.element),
                        props:{
                            style: {backgroundColor: "red", width: 50, height: 50},
                            testID: "white-green-red"
                        },
                        type: {$$typeof: Symbol(react.forward_ref), displayName: "View"}
                    }],
                    style: {backgroundColor: "green", width: 100, height: 100},
                    testID: "white-green",
                },
            },
            {
                $$typeof: Symbol(react.element),
                type: {displayName: "TouchableOpacity"},
                props: {
                    style: {backgroundColor: "blue", width: 50, height: 50}, 
                    testID: "white-blue", 
                    activeOpacity: 0.2, 
                    onPress: ?
                },
            }
        ],
        style: {backgroundColor: "white", paddingTop: 64},
        testID: "white",
    },
}

4.驅(qū)動Native執(zhí)行渲染流程

  • 0.RCTUIManager簡析
    前面提到RCTUIManager是渲染流程的集大成者,分析渲染流程,得先分析RCTUIManager的多個容器和隊列ShadowQueue。渲染流程同樣采用批處理思想。

    • _rootViewTags:根控件集合,存放所有根控件,UI布局計算就是從根控件開始遞歸結(jié)點樹。

    • _pendingUIBlocks:UI操作集合,創(chuàng)建控件、設(shè)置屬性、改變視圖層級、設(shè)置布局信息…等UI操作會先緩存,在特定時機派發(fā)到主線程。

    • _shadowViewRegistry:布局結(jié)點集合,存放整個應(yīng)用的布局結(jié)點

    • _viewRegistry:原生控件集合,存放整個應(yīng)用的原生控件

    • _shadowViewsWithUpdatedProps:已更新屬性的shadowView集合,記錄更新了屬性值的布局結(jié)點,布局時機一到會遍歷該集合逐一更新布局結(jié)點的YOGA值

    • _shadowViewsWithUpdatedChildren:已更新子控件的shadowView集合,記錄子控件有變動的布局結(jié)點,布局時機一到會遍歷該集合逐一更新控件的子控件。

    • 渲染過程大多數(shù)操作發(fā)生在隊列ShadowQueue中,它是串行隊列,作為RCTUIManager導(dǎo)出函數(shù)的執(zhí)行隊列。
      上述容器的使用有嚴(yán)格的線程規(guī)定,以保證線程安全和UI操作在主線程執(zhí)行。所有容器的創(chuàng)建、銷毀在主線程,使用(增刪改查)則有差別,_viewRegistry涉及UI操作(創(chuàng)建控件)需要在主線程使用,其他容器其實都是從數(shù)據(jù)層面的操作,并非正在執(zhí)行UI操作,因此都在ShadowQueue執(zhí)行;布局計算也在ShadowQueue執(zhí)行(異步計算布局結(jié)果)。

  • 注:通過簡潔的線程管理來實現(xiàn):JS&Native(ReactNativeRenderer&RCTUIManager)的交互、控件層次結(jié)構(gòu)(數(shù)據(jù)層面)更新、布局結(jié)點屬性更新、布局計算等都在ShadowQueue隊列執(zhí)行;控件屬性更新、更改視圖層級、渲染等正在操作UI則主線程執(zhí)行。即:非UI操作在ShadowQueue執(zhí)行,操作UI在主線程進行,結(jié)合批處理機制,達到為主線程減負、高效渲染的目的。

@implementation RCTUIManager
{
  // 根控件集合 reactTag <reactTag>
  NSMutableSet<NSNumber *> *_rootViewTags;
  // 暫存UI操作;
  NSMutableArray<RCTViewManagerUIBlock> *_pendingUIBlocks;
  // 布局結(jié)點集合 { reactTag : RCTShadowView }
  NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
  
  // 控件集合 {reactTag : UIView}
  NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
  
  // 已更新屬性的shadowView集合 { RCTShadowView: [props key] }
  NSMapTable<RCTShadowView *, NSArray<NSString *> *> *_shadowViewsWithUpdatedProps; // UIManager queue only.
  // 已更新子控件的shadowView集合
  NSHashTable<RCTShadowView *> *_shadowViewsWithUpdatedChildren; // UIManager queue only.
  ...
}

1.JS端根據(jù)計算結(jié)果,驅(qū)動原生端創(chuàng)建原生控件

  • JS端根據(jù)計算結(jié)果,驅(qū)動原生端創(chuàng)建原生控件
    • 生成reactTag,作為每個控件的標(biāo)識;
    • 從原生控件信息注冊機獲取對應(yīng)的控件信息,并處理控件屬性(過濾非法屬性……);
    • 調(diào)用原生模塊UIManager創(chuàng)建原生控件
// ReactNativeRenderer-dev.js

function createInstance(
  type,
  props,
  rootContainerInstance,
  hostContext,
  internalInstanceHandle
) {
  // 創(chuàng)建組件tag
  var tag = allocateTag();
  // 獲取原生控件信息
  var viewConfig = ReactNativeViewConfigRegistry.get(type);
  var updatePayload = create(props, viewConfig.validAttributes);
  
  // 調(diào)用原生模塊,創(chuàng)建原生控件
  UIManager.createView(
    tag, // reactTag
    viewConfig.uiViewClassName, // viewName
    rootContainerInstance, // rootTag
    updatePayload // props
  );

  var component = new ReactNativeFiberHostComponent(tag, viewConfig);
  precacheFiberNode(internalInstanceHandle, tag);
  updateFiberProps(tag, props);
  return component;
}
  • 原生端創(chuàng)建原生控件和對應(yīng)的布局結(jié)點;設(shè)置布局屬性、控件屬性值;
    設(shè)置屬性的過程相對冗長,詳見源碼,大致流程是:根據(jù)屬性名、是否是布局結(jié)點(isShadowView),構(gòu)建導(dǎo)出屬性的導(dǎo)出函數(shù)SEL以獲得屬性類型,進而構(gòu)建出屬性setter,并包裝為Block,執(zhí)行Block設(shè)置屬性,最終控件屬性值設(shè)置到原生控件,布局屬性值設(shè)置到布局結(jié)點ShadowView
// RCTUIManager.m

// 創(chuàng)建原生控件
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
                  viewName:(NSString *)viewName
                  rootTag:(nonnull NSNumber *)rootTag
                  props:(NSDictionary *)props)
{  
  RCTComponentData *componentData = _componentDataByName[viewName];
 
  // 創(chuàng)建對應(yīng)的shadowView,存入容器_shadowViewRegistry,設(shè)置屬性
  RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
  if (shadowView) {
    [componentData setProps:props forShadowView:shadowView];
    _shadowViewRegistry[reactTag] = shadowView;
    RCTShadowView *rootView = _shadowViewRegistry[rootTag];
    shadowView.rootView = (RCTRootShadowView *)rootView;
  }

  // 主線程 創(chuàng)建NativeView,存入View注冊表_viewRegistry
  __block UIView *preliminaryCreatedView = nil;
  void (^createViewBlock)(void) = ^{
    if (preliminaryCreatedView) {
      return;
    }
    preliminaryCreatedView = [componentData createViewWithTag:reactTag];
    if (preliminaryCreatedView) {
      self->_viewRegistry[reactTag] = preliminaryCreatedView;
    }
  };
  RCTExecuteOnMainQueue(createViewBlock);
  
  // 設(shè)置控件屬性(對UIView的操作都先暫存在_pendingUIBlocks,批處理)
  [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
    createViewBlock();
    if (preliminaryCreatedView) {
      [componentData setProps:props forView:preliminaryCreatedView];
    }
  }];

  // 更新props,存入_shadowViewsWithUpdatedProps
  [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]];
}

2.根據(jù)計算結(jié)果,設(shè)置視圖層次結(jié)構(gòu)

// ReactNativeRenderer-dev.js

function finalizeInitialChildren( parentInstance, type, props, rootContainerInstance, hostContext) {
  if (parentInstance._children.length === 0) {
    return false;
  }
  var nativeTags = parentInstance._children.map(function(child) {
    return typeof child === "number" ? child // Leaf node (eg text): child._nativeTag;
  });

  UIManager.setChildren(
    parentInstance._nativeTag, // 父控件tag
    nativeTags // 子控件tag集合
  );
  return false;
}

原生端布局結(jié)點樹更改層次結(jié)構(gòu),此時僅從數(shù)據(jù)層面改變層次結(jié)構(gòu),并未真正addSubVew(詳見UIView+React.m)

// RCTUIManager.m

RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag
                  reactTags:(NSArray<NSNumber *> *)reactTags)
{  
  RCTSetChildren(containerTag, reactTags,(NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);
  
  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
    // 僅僅是數(shù)據(jù)層面,并不正真改成視圖層次
    RCTSetChildren(containerTag, reactTags,(NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
  }];

  [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]];
}

3.布局時機一到,真正執(zhí)行UI渲染

  • _layoutAndMount按順序做了以下幾件事情,詳見源碼或DEMO
    • _dispatchPropsDidChangeEvents向?qū)傩灾蛋l(fā)生改變的原生控件/布局結(jié)點發(fā)送消息,主要驅(qū)使布局結(jié)點更新YOGA屬性值;
    • _dispatchChildrenDidChangeEvents向視圖層次結(jié)構(gòu)發(fā)生改變的原生控件/布局結(jié)點發(fā)送消息,主要驅(qū)使原生控件更新視圖樹(addSubview);
    • 遍歷根結(jié)點,調(diào)用uiBlockWithLayoutUpdateForRootView,封裝渲染操作。
      先驅(qū)動根結(jié)點執(zhí)行l(wèi)ayout操作,底層會從根結(jié)點開始遞歸,使用yoga計算布局結(jié)果(shadowView.layoutMetrics);
      再把正真的布局(reactSetFrame)、動畫(performAnimations)操作封裝為一個uiBlock并收集到UI操作容器_pendingUIBlocks
    • 最后調(diào)用flushUIBlocksWithCompletion,統(tǒng)一把所有UI操作派發(fā)到主線程執(zhí)行,包括最后一個渲染/動畫操作block
// RCTUIManager.m

// 布局時機
- (void)batchDidComplete
{
  [self _layoutAndMount];
}

- (void)_layoutAndMount
{
  // 通知 RCTShadowView、RCTComponent 屬性已更新
  [self _dispatchPropsDidChangeEvents];
  // 通知 RCTShadowView、RCTComponent 控件/布局結(jié)點層次結(jié)構(gòu)已更新(subView已改變)
  [self _dispatchChildrenDidChangeEvents];

  // 構(gòu)建一個UI渲染、執(zhí)行布局動畫的Block,添加到_pendingUIBlocks
  for (NSNumber *reactTag in _rootViewTags) {
    RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag];
    [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
  }

  // 統(tǒng)一執(zhí)行所有 _pendingUIBlocks,包括UI渲染
  [self flushUIBlocksWithCompletion:^{
    [self->_observerCoordinator uiManagerDidPerformMounting:self];
  }];
}

// 構(gòu)建一個UI渲染、執(zhí)行布局動畫的Block
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
{
    ...
    return block;
}
  • 布局時機理解:渲染過程中布局的時機是batchDidComplete,追溯回JSIExecutor.cpp可知這個時間點就是執(zhí)行Native call JS、 JS callback后把所有JS端暫存JS call Native調(diào)用信息傳遞給原生端,原生端發(fā)起執(zhí)行(并未執(zhí)行完畢)后,就觸發(fā)布局。為何在這個時間點布局,筆者的猜想如下(不一定準(zhǔn)確,歡迎探討):
    • 1.首先明確一個前提:UI渲染的優(yōu)先級無疑是最高的,原生開發(fā)中UI操作都是在主線程進行。因此JS端的計算結(jié)果(通常表現(xiàn)為原生控件信息的更新,即UI數(shù)據(jù)的更新)必須及時發(fā)送到原生端,執(zhí)行UI更新。
    • 2.再回顧前言對ReactNative渲染原理的介紹:用戶行為觸發(fā)原生UI控件交互事件/回調(diào)事件從原生端傳遞到JS端,JS端處理業(yè)務(wù)邏輯,得到新的UI數(shù)據(jù),再次傳遞到原生端,驅(qū)動原生模塊進行新一輪UI更新。
      • 例子1:用戶點擊按鈕觸發(fā)一個彈窗。點擊事件在RCTRootContentView通過RCTTouchHandlerRCTEventDispatcher,最終調(diào)用JS模塊RCTEventEmitter函數(shù)receiveEvent通知JS端處理UI交互事件(Native call JS)。JS端收到信號后進行業(yè)務(wù)邏輯處理,計算結(jié)果是帶彈窗的新UI數(shù)據(jù)。數(shù)據(jù)需要立即傳遞到原生端進行UI更新,繪制彈窗,在Native Call JS執(zhí)行完畢后進行無疑是最及時的,這就是布局時機1。
      • 例子2:用戶調(diào)用原生社交分享模塊,完畢需要顯示toast條提示用戶分享結(jié)果。JS端發(fā)起一個帶回調(diào)JS call Native調(diào)起原生模塊進行社交分享,原生端分享完畢,執(zhí)行JS callback通知JS端處理分享結(jié)果。JS端收到信號后進行業(yè)務(wù)邏輯處理,計算結(jié)果是帶toast提示條的新UI數(shù)據(jù)。數(shù)據(jù)需要立即傳遞到原生端進行UI更新,繪制toast提示條,在JS callback執(zhí)行完畢后進行是最及時的,這就是布局時機2。

至此,UI渲染的基本流程就分析完畢了。

六、JS端調(diào)用原生控件導(dǎo)出函數(shù)

JS端調(diào)用原生控件的導(dǎo)出函數(shù),流程如下

  • 1.JS端調(diào)用RCTUIManager導(dǎo)出函數(shù)dispatchViewManagerCommand,傳遞reactTag、Command
  • 2.觸發(fā)原生端走JS call Native流程的后半部分,最終定位到對應(yīng)原生控件的函數(shù)(可參考WebView.ios.js
// RCTUIManager.m

/*
 向ViewManager派發(fā)命令:用于RN端調(diào)用控件導(dǎo)出函數(shù)
 reactTag 控件標(biāo)簽
 commandID 命令id
 commandArgs 參數(shù)
 */
RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
                  commandID:(NSInteger)commandID
                  commandArgs:(NSArray<id> *)commandArgs)
{
  RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
  RCTComponentData *componentData = _componentDataByName[shadowView.viewName];
  Class managerClass = componentData.managerClass;
  RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
  id<RCTBridgeMethod> method = moduleData.methods[commandID];

  // 帶上reactTag,用于獲取對應(yīng)Native控件
  NSArray *args = [@[reactTag] arrayByAddingObjectsFromArray:commandArgs];
  // 調(diào)用控件導(dǎo)出函數(shù)
  [method invokeWithBridge:_bridge module:componentData.manager arguments:args];
}
// RCTWKWebViewManager.m

RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
{
  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
    RCTWKWebView *view = viewRegistry[reactTag];
    if (![view isKindOfClass:[RCTWKWebView class]]) {  
    } else {
      [view goBack];
    }
  }];
}

七、結(jié)語

  • 這一系列文章就到此為止了,希望有助于大家理解ReactNative源碼;
  • ReactNative還有很多設(shè)計值得研究,例如原生如何端驅(qū)動JS端定時任務(wù)、觀察者模式的設(shè)計等;

Reference

ReactNative中文網(wǎng)
React
ReactNative源碼解析——渲染機制詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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