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中全域性變數中儲存起來。