ReactNative原理与核心知识点

React Native特点
跨平台
使用js写出页面组件代码被React框架统一转成Virtual DOM树,Virtual DOM树是UI结构的一层抽象,可以被转换成任何支持端的UI视图。
ReactNative框架将Virtual DOM 转成原APP的UIView树。
热修复
ReactNative的产物bundle包,bundle包中包含的为RN业务所需要的所有资源,包括代码和资源。bundle的加载方式是APP启动时从后台下载,然后通过js虚拟机执行的。
所以可以将每次业务迭代修改后的代码上传到服务,进行用户无感知版本更新。
注意:
bundle中的业务代码不能修改APP现有的原生行为,不能调用私有API,不然禁止上架。
bundle包中的js是经过babel转义后的普通js,而非jsx语法糖。
ReactNative原理与核心知识点

 

JS与Native交互的基本原理
JS引擎
iOS侧使用的JavaScriptCore作为bundle产物的js执行引擎。
JS与Native交互的基本原理很简单,就是在JS的全局上下文添加成员变量。原生调用JS是JS在JS上下文中添加方法成员变量,然后原生调用。JS调用原生是原生往JS上下文中添加方法成员变量,然后JS调用。
JS调用原生
通过将block对象赋值给js全局上下文中的全局变量,js内部调用这个全局方法进行执行。
ctx[@"NativeMethod"] = ^(NSString *name) {     // do something     return someResult }

原生调用JS
先创建一个JS上下文对象,在上下文中添加方法的全局变量。原生获取上下文的全局变量Value, 然后调用,执行这个JS方法。
// 创建一个ctx的JS上下文 JSContent *ctx = [[JSContent alloc] init]; // 创建一个变量name [ctx evaluateScript:@"var name = 'jack'"]; // 创建一个方法 [ctx evaluateScript:@"var sayHi = function(name) { return 'hello ' + name }"];   // 通过ctx上下文对象,获取到hello方法 JSValue *sayHiUnction = ctx[@"sayHi"]; // 运行js方法 JSValue *greetings = [sayHiUnction callWithArguments:@[@"jack"]; // hello jack

ReactNative框架中原生与JS的调用基本思路也是这种,只不过考虑到大量的Native对象注册会污染js引擎中的上下文,增加了一层Bridge。
原生和JS之间的交互都是通过Bridge这个通道,通过里面的几个基础方法进行交互。原生与JS的交互是异步的。
另外,Facebook为了提升RN框架中JS的执行效率,专门推出了一个JS引擎 Hermes, 在关键指标上,相比于JSCore,V8提升了不少,比较易于RN框架集成。

 

ReactNative核心知识

RCTBridge: ReactNative中原生与JS交互的通道
RCTBridge用于给js引擎提供原生扩展接口。将原生功能如定位,3D等通过Bridge将其封装成JS接口,然后注入到js引擎的上下文中。
RN框架启动的简单流程为:首先将js代码加载到内存,然后创建RCTBridge实例,然后创建RCTRootContentView内容展示的容器视图,然后调用JS上下文中AppRegistry对象的runApplication方法,并将@[moduleName, appParameters]组件名,参数传递给JS。
// RCTRootView.m - (void)javaScriptDidLoad:(NSNotification *)notification {   RCTAssertMainQueue();   RCTBridge *bridge = notification.userInfo[@"bridge"];   if (bridge != _contentView.bridge) {     [self bundleFinishedLoading:bridge];   } }  - (void)bundleFinishedLoading:(RCTBridge *)bridge {   // 省略创建RCTRootContentView...    [self runApplication:bridge];    // 省略添加一个RCTRootContentView... }  - (void)runApplication:(RCTBridge *)bridge {   NSString *moduleName = _moduleName ?: @""; // 这里是@"NewProject"   NSDictionary *appParameters = @{     @"rootTag": _contentView.reactTag,     @"initialProps": _appProperties ?: @{},   };    [bridge enqueueJSCall:@"AppRegistry"                 method:@"runApplication"                   args:@[moduleName, appParameters]              completion:NULL]; }

 
原生调用JS
在JS上下文中,调用JS的方式是通过方法:global.batchedBridge.callFunctionReturnFlushedQueue
ReactNative原理与核心知识点

 

所以RN在原生侧的的JS引擎的封装对象中使用成员变量保存了这个JS的函数指针,原生调用JS时,通过传递参数 moduleid 和 methodid 完成方法的调用。
void JSIExecutor::bindBridge() {   std::call_once(bindFlag_, [this] {     SystraceSection s("JSIExecutor::bindBridge (once)");     Value batchedBridgeValue =         runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");     if (batchedBridgeValue.isUndefined() || !batchedBridgeValue.isObject()) {       throw JSINativeException(           "Could not get BatchedBridge, make sure your bundle is packaged correctly");     }      Object batchedBridge = batchedBridgeValue.asObject(*runtime_);     callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(         *runtime_, "callFunctionReturnFlushedQueue");     invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(         *runtime_, "invokeCallbackAndReturnFlushedQueue");     flushedQueue_ =         batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");   }); }

 
JS调用原生
JS调用原生通常是通过原生主动处理_eventQueue中的事件,特殊情况会直接调用原生注册给JS的nativeFlushQueueImmediate方法, 并传递moduleName 、methodName、callback 参数给这个方法完成调用。
ReactNative原理与核心知识点

 

void JSIExecutor::initializeRuntime() {   SystraceSection s("JSIExecutor::initializeRuntime");   runtime_->global().setProperty(       *runtime_,       "nativeModuleProxy",       Object::createFromHostObject(           *runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));    runtime_->global().setProperty(       *runtime_,       "nativeFlushQueueImmediate",       Function::createFromHostFunction(           *runtime_,           PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),           1,           [this](               jsi::Runtime &,               const jsi::Value &,               const jsi::Value *args,               size_t count) {             if (count != 1) {               throw std::invalid_argument(                   "nativeFlushQueueImmediate arg count must be 1");             }             callNativeModules(args[0], false);             return Value::undefined();           }));    runtime_->global().setProperty(       *runtime_,       "nativeCallSyncHook",       Function::createFromHostFunction(           *runtime_,           PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),           1,           [this](               jsi::Runtime &,               const jsi::Value &,               const jsi::Value *args,               size_t count) { return nativeCallSyncHook(args, count); }));    runtime_->global().setProperty(       *runtime_,       "globalEvalWithSourceUrl",       Function::createFromHostFunction(           *runtime_,           PropNameID::forAscii(*runtime_, "globalEvalWithSourceUrl"),           1,           [this](               jsi::Runtime &,               const jsi::Value &,               const jsi::Value *args,               size_t count) { return globalEvalWithSourceUrl(args, count); }));    if (runtimeInstaller_) {     runtimeInstaller_(*runtime_);   }   bool hasLogger(ReactMarker::logTaggedMarker);   if (hasLogger) {     ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);   } }

Virtual DOM 虚拟DOM
 
虚拟DOM的特点
1.用于描述页面的UI结构:在作用上虚拟DOM和普通的DOM是一样的。
2.平台无关性:虚拟DOM表示的UI结构是对UI的一层抽象,它是平台无关的。具体的UI渲染是交个具体的平台渲染引擎进行的,如iOS,安卓自身的渲染引擎。
虚拟DOM对标签的定义
虚拟DOM把标签分为2类:原子型标签,组合型标签。
原子型标签是平台支持型的基础标签,如果RCTView, RCTText。对应浏览器页面中,原子型标签有h1,li,div等。
组合型标签是用户自定义的组件,它在虚拟DOM中对应的是自定义标签构造器函数,页面渲染时调用这个构造函数,创建一个实例,然后调用实例的render方法,组合型标签的render方法内会把组合标签进行拆解,最后拆解成基本的原子型标签。
var ele = {     ...     type: type, // 元素的类型     key: key, // 元素key标示     ref: ref, // 元素的引用     props: props, // 元素的参数,包含children     ... }  // example 1 <div>hello</div> // 会被描述为  {type: 'div',     props: {         children: ['hello']     } }  // example 2 <CustomerComponents /> // 会被描述为 {     type: CustomerComponents }

 
UI渲染
RN框架与浏览器的对比:在浏览器中,JS通过调用DOM API创建UI视图。在RN中,JS通过调用RCTUIManager来创建iOS,Android移动端的UI视图。
RN的UI渲染是基于虚拟DOM的,通过根据不同的平台调用不同平台的Bridge, Brideg再调用不同平台的的RCTUIManager进行UI的创建。
 

其他

三条线程
RN内部有三条线程在同时运行着:Shadow Thread, JS Thread, UI Thread。
JS Thread:JS线程,负责JS与原生的交互,它们的交互是异步的,每次调用都是将block放入队列中,等js代码执行完后,读取事件队列进行处理。
UI Thread:UI主线程,负责页面的交互与渲染, 由RCTUIManager使用。
Shadow Thread: 负责将flex布局转成Native的布局,由yago引擎使用。

三个队列
RN框架内,原生与JS的交换类型分两种:UI和事件,这2这种事件的处理都是异步的,它们都是将事件顺序放置到队列中,在合适的时机被调用。
事件的处理在RCTBridge中处理,UI的处理在RCTUIManager中处理。
JS调用原生异步事件队列:_eventQueue队列
原生调用JS异步事件队列:_pendingCalls队列
UI更新异步事件处理队列:_pendingUIBlocks队列

JSI
javascript interface js虚拟机通用接口层,是针对JS引擎封装的上层API框架,使用JSI做JS引擎调用的优点:
1.底部可以任意替换JS引擎而不影响上层JS引擎的使用。如:可以任意替换JavaScript Core, V8等。
2.通过JSI,JavaScript可以持有C++宿主对象的引用,所以可以直接调用原生方法(UIView, NativeModule),它与现在统一使用Bridge这个通道和消息异步调用比起来,提高了消息发送的及时性,避免了消息队列执行的等待。

React Native核心知识在框架中的使用
 
React Native核心功能在RN项目启动时会进行各自的初始化,生成bundle运行上下文。在类型上可以分为2类:
1.JS与原生的事件处理:创建RCTBridge桥接通道。
2.UI交互与更新的事件处理:创建RCTRootView容器视图。
APP启动,React Native运行环境初始化。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {   if (!self.bridge) {     self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions];   }    NSDictionary *initProps = [self prepareInitialProps];   UIView *rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps];    if (@available(iOS 13.0, *)) {     rootView.backgroundColor = [UIColor systemBackgroundColor];   } else {     rootView.backgroundColor = [UIColor whiteColor];   }    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];   UIViewController *rootViewController = [self createRootViewController];   rootViewController.view = rootView;   self.window.rootViewController = rootViewController;   [self.window makeKeyAndVisible];   return YES; }

 
JS与原生的事件处理:创建RCTBridge桥接通道
RCTBridge的主要逻辑是在batchedBridge中,主要初始化流程为:
1.初始化Native Modules
2.创建Native Modules配置表
3.准备JS引擎工厂,创建JS引擎
4.将Modules配置信息注册到JS引擎中
5.加载boundle代码
6.执行boundle代码
- (void)setUp {   RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil);    //_performanceLogger日志工具初始化    //_bundleURL获取    //batchedBridge创建    self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];   [self.batchedBridge start];    RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); }  batchedBridge是RCTCXXBridge,它的初始化方法如下: - (instancetype)initWithParentBridge:(RCTBridge *)bridge {   RCTAssertParam(bridge);    if ((self = [super initWithDelegate:bridge.delegate                             bundleURL:bridge.bundleURL                        moduleProvider:bridge.moduleProvider                         launchOptions:bridge.launchOptions])) {     _parentBridge = bridge;     _performanceLogger = [bridge performanceLogger];      registerPerformanceLoggerHooks(_performanceLogger);      /**      * Set Initial State      */     _valid = YES;     _loading = YES;     _moduleRegistryCreated = NO;     _pendingCalls = [NSMutableArray new];     _displayLink = [RCTDisplayLink new];     _moduleDataByName = [NSMutableDictionary new];     _moduleClassesByID = [NSMutableArray new];     _moduleDataByID = [NSMutableArray new];     _objCModuleRegistry = [RCTModuleRegistry new];     [_objCModuleRegistry setBridge:self];     _bundleManager = [RCTBundleManager new];     [_bundleManager setBridge:self];     _viewRegistry_DEPRECATED = [RCTViewRegistry new];     [_viewRegistry_DEPRECATED setBridge:self];     _callableJSModules = [RCTCallableJSModules new];     [_callableJSModules setBridge:self];      [RCTBridge setCurrentBridge:self];      [[NSNotificationCenter defaultCenter] addObserver:self                                              selector:@selector(handleMemoryWarning)                                                  name:UIApplicationDidReceiveMemoryWarningNotification                                                object:nil];      RCTLogSetBridgeModuleRegistry(_objCModuleRegistry);     RCTLogSetBridgeCallableJSModules(_callableJSModules);   }   return self; }   - (void)start {   RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge start]", nil);    [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification                                                       object:_parentBridge                                                     userInfo:@{@"bridge" : self}];    //启动JS线程   _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];   _jsThread.name = RCTJSThreadName;   _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive; #if RCT_DEBUG   _jsThread.stackSize *= 2; #endif   [_jsThread start];    dispatch_group_t prepareBridge = dispatch_group_create();    [_performanceLogger markStartForTag:RCTPLNativeModuleInit];    //1.初始化Native Modules   [self registerExtraModules];   //2.创建Native Modules配置表   // Initialize all native modules that cannot be loaded lazily   (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];   [self registerExtraLazyModules];    [_performanceLogger markStopForTag:RCTPLNativeModuleInit];    // This doesnt really do anything.  The real work happens in initializeBridge.   _reactInstance.reset(new Instance);    __weak RCTCxxBridge *weakSelf = self;    // 3.准备JS引擎工厂,创建JS引擎   std::shared_ptr<JSExecutorFactory> executorFactory;   if (!self.executorClass) {     if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {       id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>)self.delegate;       executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];     }     // 4.将Modules配置信息注册到JS引擎中     if (!executorFactory) {       auto installBindings = RCTJSIExecutorRuntimeInstaller(nullptr); #if RCT_USE_HERMES       executorFactory = std::make_shared<HermesExecutorFactory>(installBindings); #else       executorFactory = std::make_shared<JSCExecutorFactory>(installBindings); #endif     }   } else {     id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];     executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {       if (error) {         [weakSelf handleError:error];       }     }));   }      //_turboModuleRegistry是一个TurboModule注册表,TurboModule是JS在RN中的一种优化方式,将常用的JS代码编译成可执行代码,提高执行速度。   /**    * id<RCTCxxBridgeDelegate> jsExecutorFactory may create and assign an id<RCTTurboModuleRegistry> object to    * RCTCxxBridge If id<RCTTurboModuleRegistry> is assigned by this time, eagerly initialize all TurboModules    */   if (_turboModuleRegistry && RCTTurboModuleEagerInitEnabled()) {     for (NSString *moduleName in [_turboModuleRegistry eagerInitModuleNames]) {       [_turboModuleRegistry moduleForName:[moduleName UTF8String]];     }      for (NSString *moduleName in [_turboModuleRegistry eagerInitMainQueueModuleNames]) {       if (RCTIsMainQueue()) {         [_turboModuleRegistry moduleForName:[moduleName UTF8String]];       } else {         id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;         dispatch_group_async(prepareBridge, dispatch_get_main_queue(), ^{           [turboModuleRegistry moduleForName:[moduleName UTF8String]];         });       }     }   }    // Dispatch the instance initialization as soon as the initial module metadata has   // been collected (see initModules)   dispatch_group_enter(prepareBridge);   [self ensureOnJavaScriptThread:^{     [weakSelf _initializeBridge:executorFactory];     dispatch_group_leave(prepareBridge);   }];    // 5.加载boundle代码   // Load the source asynchronously, then store it for later execution.   dispatch_group_enter(prepareBridge);   __block NSData *sourceCode;   [self       loadSource:^(NSError *error, RCTSource *source) {         if (error) {           [weakSelf handleError:error];         }          sourceCode = source.data;         dispatch_group_leave(prepareBridge);       }       onProgress:^(RCTLoadingProgress *progressData) { }];    // 模块和js代码加载完成后,执行js代码   // Wait for both the modules and source code to have finished loading   dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{     RCTCxxBridge *strongSelf = weakSelf;     if (sourceCode && strongSelf.loading) {       // 6.执行boundle代码       [strongSelf executeSourceCode:sourceCode sync:NO];     }   });   RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); }

 
初始化Native Modules与创建Native Modules配置表
把本地的RN模块都收集起来,包括RN框架自带的和用户自定义的,将模块信息保存到Bridge的变量中,用于与JS交换。
_moduleDataByName = [NSMutableDictionary new];
_moduleClassesByID = [NSMutableArray new];
_moduleDataByID = [NSMutableArray new];
 
JS发送消息到Native时,通过- (id)moduleForName:(const char *)moduleName;查询到模块详情,进行模块调用。
_objCModuleRegistry = [RCTModuleRegistry new];
[_objCModuleRegistry setBridge:self];

准备JS引擎工厂,创建JS引擎与将Modules配置信息注册到JS引擎中
RN将Native Modules信息收集完成后保存到成员变量中,这个成员变量是一个数组。使用moduleConfig保存模块的模块名,方法名。然后将这些数据注入到JS引擎中。
JS调用原生时,通过模块名,方法名,参数调用原生方法。在原生调用JS时,会将调用放入_pendingCalls队列中,进行异步执行。而JS调原生是将调用放入到_eventQueue队列中,进行异步执行。
JS可以通过方法nativeFlushQueueImmediate直接调用Native,但是一般JS不会这样做,而是等原生自己去_eventQueue队列中自己去取任务做处理。
// js thread only (which surprisingly can be the main thread, depends on used JS executor) - (void)flushEventsQueue {   [_eventQueueLock lock];   NSDictionary *events = _events;   _events = [NSMutableDictionary new];   NSMutableArray *eventQueue = _eventQueue;   _eventQueue = [NSMutableArray new];   _eventsDispatchScheduled = NO;   [_eventQueueLock unlock];    for (NSNumber *eventId in eventQueue) {     [self dispatchEvent:events[eventId]];   } }

 
UI交互与更新的事件处理:创建RCTRootView容器视图
 
RCTRootView为RN页面的入口,在RCTRootView初始化过程中,会创建RCTRootContentView作为内容视图放置在RCTRootView的底部作为根视图。
RCTRootContentView的初始化方法中,在uiManager中将RCTRootContentView注册成根视图。
- (instancetype)initWithFrame:(CGRect)frame                        bridge:(RCTBridge *)bridge                      reactTag:(NSNumber *)reactTag                sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility {   if ((self = [super initWithFrame:frame])) {     _bridge = bridge;     self.reactTag = reactTag;     _sizeFlexibility = sizeFlexibility;     _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];     [_touchHandler attachToView:self];     [_bridge.uiManager registerRootView:self];   }   return self; }

RCTUIManager是RN中UI的管理者,它负责处理所有与UI相关的事情,如:注入到JS中的创建View方法。
在createView方法中可以看到,RN对View的操作都是双份的,分别作用在RCTShadowView和UIView上。RCTShadowView和UIView的关系类似于虚拟DOM和DOM的关系。
RCTShadowView是一个虚拟DOM树,是一个结构体,用于描述视图的样式和事件,比较轻量级。
在RN中当调用setState更新组件状态时,就会生成一个新的虚拟DOM,然后RN将新的虚拟DOM与旧的虚拟DOM进行Diff对比,生成差异对象,然后遍历差异对象,将所有的改动更新到UI上。而更新到了Native是先更新到RCTShadowView上,等合适的时候再统一更新到UI上。

UI更新操作也是异步的,更新任务被放置在_pendingUIBlocks队列上,在UI变化时或Bridge批出来结束时刷新这个队列。
RCT_EXPORT_METHOD(createView                   : (nonnull NSNumber *)reactTag viewName                   : (NSString *)viewName rootTag                   : (nonnull NSNumber *)rootTag props                   : (NSDictionary *)props) {   RCTComponentData *componentData = _componentDataByName[viewName];   if (componentData == nil) {     RCTLogError(@"No component found for view with name "%@"", viewName);   }    // Register shadow view   RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];   if (shadowView) {     [componentData setProps:props forShadowView:shadowView];     _shadowViewRegistry[reactTag] = shadowView;     RCTShadowView *rootView = _shadowViewRegistry[rootTag];     RCTAssert(         [rootView isKindOfClass:[RCTRootShadowView class]] || [rootView isKindOfClass:[RCTSurfaceRootShadowView class]],         @"Given `rootTag` (%@) does not correspond to a valid root shadow view instance.",         rootTag);     shadowView.rootView = (RCTRootShadowView *)rootView;   }    // Dispatch view creation directly to the main thread instead of adding to   // UIBlocks array. This way, it doesnt get deferred until after layout.   __block UIView *preliminaryCreatedView = nil;    void (^createViewBlock)(void) = ^{     // Do nothing on the second run.     if (preliminaryCreatedView) {       return;     }      preliminaryCreatedView = [componentData createViewWithTag:reactTag rootTag:rootTag];      if (preliminaryCreatedView) {       self->_viewRegistry[reactTag] = preliminaryCreatedView;     }   };    // We cannot guarantee that asynchronously scheduled block will be executed   // *before* a block is added to the regular mounting process (simply because   // mounting process can be managed externally while the main queue is   // locked).   // So, we positively dispatch it asynchronously and double check inside   // the regular mounting block.    RCTExecuteOnMainQueue(createViewBlock);    [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {     createViewBlock();      if (preliminaryCreatedView) {       [componentData setProps:props forView:preliminaryCreatedView];     }   }];    [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]]; } 

 

参考文章
https://juejin.cn/post/6916452544956858382#heading-11
https://juejin.cn/post/6844904184542822408
https://juejin.cn/post/6844904184500715527

发表评论

评论已关闭。

相关文章