4.生成模块配置表并写入JavaScript端
复习一下 nativeRequireModuleConfig 这个 Block,它可以接受 ModuleName 并且生成详细的模块信息,但在前文中我们没有提到 JavaScript 是如何知道 Objective-C 要暴露哪些类的(目前只是 Objective-C 自己知道)。
这一步的操作就是为了让 JavaScript 获取所有模块的名字
- (NSString *)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); }
5.执行JavaScript代码
这一步也没什么技术难度可以,代码已经加载进了内存,该做的配置也已经完成,只要把 JavaScript 代码运行一遍即可。
运行代码时,第三步中所说的那些 Block 就会被执行,从而向 JavaScript 端写入配置信息。
至此,JavaScript 和 Objective-C 都具备了向对方交互的能力,准备工作也就全部完成了。
方法调用
如前文所述,在 React Native 中,Objective-C 和 JavaScript 的交互都是通过传递 ModuleId、MethodId 和 Arguments 进行的。以下是分情况讨论
OC 调用 JavaScript
也许你在其他文章中曾经多次听说 JavaScript 代码总是在一个单独的线程上面调用,它的实际含义是 Objective-C 会在单独的线程上运行 JavaScript 代码
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block { if ([NSThread currentThread] != _javaScriptThread) { [self performSelector:@selector(executeBlockOnJavaScriptQueue:) onThread:_javaScriptThread withObject:block waitUntilDone:NO]; } else { block(); } }
调用JavaScript的核心代码如下
- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete{ [self executeBlockOnJavaScriptQueue:^{ // 获取 contextJSRef、methodJSRef、moduleJSRef resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, arguments.count, jsArgs, &errorJSRef); objcValue = /*resultJSRef 转换成 Objective-C 类型*/ onComplete(objcValue, nil); }]; }
需要注意的是,这个函数名是我们要调用 JavaScript 的中转函数名,比如 callFunctionReturnFlushedQueue。也就是说它的作用其实是处理参数,而非真正要调用的 JavaScript 函数。
在实际使用的时候,我们可以这样发起对 JavaScript 的调用:
[_bridge.eventDispatcher sendAppEventWithName:@"greeted" body:@{ @"name": @"nmae"}];
这里的 Name 和 Body 参数分别表示要调用的 JavaScript 的函数名和参数。
JavaScript调用OC
在调用 Objective-C 代码时,如前文所述,JavaScript 会解析出方法的 ModuleId、MethodId 和 Arguments 并放入到 MessageQueue 中,等待 Objective-C 主动拿走,或者超时后主动发送给 Objective-C。
Objective-C 负责处理调用的方法是 handleBuffer,它的参数是一个含有四个元素的数组,每个元素也都是一个数组,分别存放了 ModuleId、MethodId、Params,第四个元素目测用处不大。
函数内部在每一次方调用中调用 _handleRequestNumber:moduleID:methodID:params 方法,通过查找模块配置表找出要调用的方法,并通过 runtime 动态的调用:
演示JavaScript调用OC方法:
//.h文件 #import <Foundation/Foundation.h> #import "RCTBridge.h" #import "RCTLog.h" #import "EncryptUtil.h" #import "RSA.h" @interface CryptoExport : NSObject<RCTBridgeModule> @end //.m文件 #import "CryptoExport.h" @implementation CryptoExport RCT_EXPORT_MODULE()//必须定义的宏 RCT_EXPORT_METHOD(rsaEncryptValue:(NSString *)src withKey:(NSString *)rsaKey successCallback:(RCTResponseSenderBlock)successCallback){ NSString *rsaValue = [RSA encryptString:src publicKey:rsaKey]; successCallback(@[rsaValue]); } @end
每个oc的方法前必须加上 RCT_EXPORT_METHOD 宏,用来注册模块表。
在JavaScript中的调动如下
NativeModules.CryptoExport.rsaEncryptValue(value, rsaKey,function (rsaValue) { console.log(rsaValue) });
React Native更新机制
之前我们说过,React是状态机,就是不停的去检查当前的状态,判断是否需要刷新。
调用this.setState
ReactClass.prototype.setState = function(newState) { this._reactInternalInstance.receiveComponent(null, newState); }
调用内部receiveComponent方法,这里在接受元素的时候主要分三种情况:
- 文本元素
- 基本元素
- 自定义元素
文本元素
ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) { //跟以前保存的字符串比较 if (nextText !== this._currentElement) { this._currentElement = nextText; var nextStringText = '' + nextText; if (nextStringText !== this._stringText) { this._stringText = nextStringText; var commentNodes = this.getHostNode(); // 替换文本元素 DOMChildrenOperations.replaceDelimitedText( commentNodes[0], commentNodes[1], nextStringText ); } } }
基本元素
ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) { var prevElement = this._currentElement; this._currentElement = nextElement; this.updateComponent(transaction, prevElement, nextElement, context); }
updateComponent 方法
ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) { // 略..... //需要单独的更新属性 this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag); //再更新子节点 this._updateDOMChildren( lastProps, nextProps, transaction, context ); // ...... }
自定义元素
ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) { var prevElement = this._currentElement; var prevContext = this._context; this._pendingElement = null; this.updateComponent( transaction, prevElement, nextElement, prevContext, nextContext ); }
updateComponent 方法
ReactCompositeComponent.prototype.updateComponent = function( transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext ){ //省略 }
调用内部 _performComponentUpdate 方法
ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() { // 判定两个element需不需要更新 if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { // 如果需要更新,就继续调用子节点的receiveComponent的方法,传入新的element更新子节点。 ReactReconciler.receiveComponent( prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context) ); } else { // 卸载之前的子节点,安装新的子节点 var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance); ReactReconciler.unmountComponent( prevComponentInstance, safely, false /* skipLifecycle */ ); var nodeType = ReactNodeTypes.getType(nextRenderedElement); this._renderedNodeType = nodeType; var child = this._instantiateReactComponent( nextRenderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var nextMarkup = ReactReconciler.mountComponent( child, transaction, this._hostParent, this._hostContainerInfo, this._processChildContext(context), debugID ); }
this._updateDOMChildren 方法内部调用diff算法
实现过程
_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var removedNodes = {}; var mountImages = []; // 获取新的子元素数组 var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context ); if (!nextChildren && !prevChildren) { return; } var updates = null; var name; var nextIndex = 0; var lastIndex = 0; var nextMountIndex = 0; var lastPlacedNode = null; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { // 同一个引用,说明是使用的同一个component,所以我们需要做移动的操作 // 移动已有的子节点 // NOTICE:这里根据nextIndex, lastIndex决定是否移动 updates = enqueue( updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) ); // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 更新component的.mountIndex属性 prevChild._mountIndex = nextIndex; } else { if (prevChild) { // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); } // 添加新的子节点在指定的位置上 updates = enqueue( updates, this._mountChildAtIndex( nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ); nextMountIndex++; } // 更新nextIndex nextIndex++; lastPlacedNode = ReactReconciler.getHostNode(nextChild); } // 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点 for (name in removedNodes) { if (removedNodes.hasOwnProperty(name)) { updates = enqueue( updates, this._unmountChild(prevChildren[name], removedNodes[name]) ); } } }