1. 程式人生 > >React Native技術剖析(一)

React Native技術剖析(一)

前言

React Native(簡稱RN)的由來、優勢、安裝使用等等不在這裡囉嗦,可以自行Google/百度。筆者整理了一下之前學習RN過程中記錄的筆記,結合RN原始碼來分析RN框架裡面的一些技術思路,這對於理解和更好地使用RN都是很有益處的。由於水平有限,肯定有一些理解不對的地方歡迎指正。
今天主要講一下,RN初始化過程是什麼步驟,都做了哪些事情。

RN初始化過程

以iOS為例,RN的渲染主要在RCTRootView中,初始化程式碼非常簡單,就是建立RootVIew物件. (由於函式呼叫層次比較深,一層層講解很容易不清楚當前在那個函式,請讀者諒解)

RCTRootView *rootView = [[RCTRootView
alloc] initWithBundleURL:jsCodeLocation moduleName:@"LarkRN" initialProperties:nil launchOptions:launchOptions];

我們看一下RCTRootView的initWithBundleURL函式做了什麼:
函式中建立一個Bridge物件,並呼叫initWithBridge函式。

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions
{
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
                                            moduleProvider:nil
launchOptions:launchOptions]; return [self initWithBridge:bridge moduleName:moduleNameinitialProperties:initialProperties]; }

Bridge物件建立過程中又呼叫了[self setUp],setUp函式呼叫createBatchedBridge函式建立了一個BatchedBridge物件

- (void)createBatchedBridge
{
  self.batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
}

BatchedBridge物件初始化中又呼叫了[self start],Start函式呼叫moduleConfig函式

    config = [weakSelf moduleConfig];

BatchedBridge物件的moduleConfig函式主要是收集所有原生模組配置資訊,返回格式化字串。

NSMutableArray<NSArray *> *config = [NSMutableArray new];
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (self.executorClass == [RCTJSCExecutor class]) {
      [config addObject:@[moduleData.name]];
    } else {
      [config addObject:RCTNullIfNil(moduleData.config)];
    }
  }
  return RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);

每個原生模組的配置資訊由RCTModuleData 物件config屬性管理,config是個陣列,其中的元素依次為模組名,匯出常量陣列,匯出函式陣列,匯出非同步函式(型別為RCTFunctionTypePromise的函式)索引陣列

NSDictionary<NSString *, id> *constants;
NSMutableArray<NSString *> *methods;
NSMutableArray<NSNumber *> *asyncMethods; 

 NSMutableArray *config = [NSMutableArray new];
  [config addObject:self.name];
  if (constants.count) {
    [config addObject:constants];
  }
  if (methods) {
    [config addObject:methods];
    if (asyncMethods) {
      [config addObject:asyncMethods];
    }
  }

BatchedBridge物件Start函式呼叫injectJSONConfiguration函式

  [weakSelf injectJSONConfiguration:config onComplete:nil

BatchedBridge物件injectJSONConfiguration函式就是向JSC中新增一個全域性變數__fbBatchedBridgeConfig

 [_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:onComplete];

接下來執行BatchedBridge物件的executeSourceCode函式,執行RN Bundle檔案(JS程式碼)

 [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError)
  {…此處省略}

BatchedBridge物件的executeSourceCode函式呼叫JSC的executeApplicationScript函式來執行JS程式碼

 [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {…}

JSC物件的executeApplicationScript函式將下載的RN Bundle檔案放入JSC中執行

    RCTJSCExecutor *strongSelf = weakSelf;
    JSValueRef jsError = NULL;
    JSStringRef execJSString = JSStringCreateWithUTF8CString((const char *)script.bytes);
    JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, _bundleURL, 0, &jsError);
    JSStringRelease(execJSString);

JS的BatchedBridge物件實際上是MessageQueue物件,使用前面的__fbBatchedBridgeConfig(原生模組配置列表)變數進行初始化

    constBatchedBridge=new MessageQueue(
        ()=>global.__fbBatchedBridgeConfig
    );

MessageQueue物件的初始化過程中建立一個RemoteModules屬性

    lazyProperty(this,'RemoteModules',()=>{
        Let {remoteModuleConfig}=configProvider();
        Let modulesConfig=this._genModulesConfig(remoteModuleConfig);
        Let modules=this._genModules(modulesConfig);
        this._genLookupTables(
            modulesConfig,this._remoteModuleTable,this._remoteMethodTable
        );
        returnmodules;
    });
    _genModules(remoteModules){
        Let modules={};
        remoteModules.forEach((config,moduleID)=>{
        Let info=this._genModule(config,moduleID);
        if(info){
            modules[info.name]=info.module;
        }
        });
        Return modules;
    }

MessageQueue物件的_genModules函式生成模型資訊物件,key值為模型名,如果模組沒有匯出任何屬性(常量或函式),則僅記錄模組ID;處理模型中匯出的函式,對函式進行包裝。

    _genModule(config,moduleID):?Object{
        Let moduleName,constants,methods,asyncMethods,syncHooks;
        if(moduleHasConstants(config)){
            [moduleName,constants,methods,asyncMethods,syncHooks]=config;
        }else{
            [moduleName,methods,asyncMethods,syncHooks]=config;
        }

        Let module={};
        Methods && methods.forEach((methodName,methodID)=>{
        Const isAsync = asyncMethods && arrayContains(asyncMethods,methodID);
        Const isSyncHook = syncHooks && arrayContains(syncHooks,methodID);
        Const methodType=isAsync? MethodTypes.remoteAsync:
        isSyncHook ? MethodTypes.syncHook:
        MethodTypes.remote;

        module[methodName]=this._genMethod(moduleID,methodID,methodType);
        });
        Object.assign(module,constants);
        if(!constants&&!methods&&!asyncMethods){
            module.moduleID=moduleID;
        }
        return{name:moduleName,module};
    }

MessageQueue物件的_genLookupTables函式生成模組名檢索表和函式檢索表

    _genLookup(config,moduleID,moduleTable,methodTable){
        Let moduleName,methods;
        if(moduleHasConstants(config)){
            [moduleName,,methods]=config;
        }else{
            [moduleName,methods]=config;
        }
        moduleTable[moduleID]=moduleName;
        methodTable[moduleID]=Object.assign({},methods);
    } 

MessageQueue物件registerCallableModule註冊JS端可呼叫模組,記錄每個模組名和可用的函式列表,供原生端呼叫

    BatchedBridge.registerCallableModule('Systrace',Systrace);
    registerCallableModule(name,methods){
        this._callableModules[name]=methods;
    }

MessageQueue物件初始化完成後,會放入到一個全域性變數”__fbBatchedBridge”中,供後續使用。

    Object.defineProperty(global,'__fbBatchedBridge',{value:BatchedBridge});

到此,RN初始化過程全部結束。簡單來說,就是收集原生和JS模組配置資訊,然後生成每個模組及其可用函式的檢索表, 並放入JSC中全域性變數中儲存起來。