React native 基本介绍

作者: FlyElephant | 来源:发表于2019-04-21 21:43 被阅读7次

    React native源自React,React 是一套可以用简洁的语法高效绘制Dom的框架。React中需要使用JSX语法,JSX是对JavaScript的扩展。JSX可以将CSS,HTML,表达式进行一起书写,简化了html代码书写的形式。

    Virtual Dom

    JSX 经过 babel 编译为 React.createElement() 的形式,其返回结果就是 Virtual DOM,最后通过 ReactDOM.render() 将 Virtual DOM 转化为真实的 DOM 展现在界面上。

    虚拟DOM是React的优势,具有批处理和高效的Diff算法。这让我们可以无需担心性能问题而”毫无顾忌”的随时“刷新”整个页面,由虚拟 DOM来确保只对界面上真正变化的部分进行实际的DOM操作。

    React native

    React 在前端取得成功之后,希望在移动端开发统一Android和iOS,于是React native随之诞生。React native可以通过JSX语法更快的实现UI布局,通过JavaScriptCore与native之间进行交互。

    JavaScript 是一种单线程的语言,它不具备自运行的能力,总是被动调用。React Native执行环境是是 Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行。

    即使使用了React Native,依然需要 UIKit 等框架,调用的是 Objective-C 代码。JavaScript 只是辅助,提供了一个简化两端UI重复开发一个工作,对于Native而言只是锦上添花,而非雪中送炭。单纯想通过React native放弃native端目前是不太现实的。

    React native 加载流程

    JavaScript Core 是一个面向 Objective-C 的框架,在 Objective-C 可以通过JSContext获取到对象,方法等各种信息,调用执行JavaScript 函数。

    React Native 解决这个问题的方案是在 Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。这样,无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId、MethodId 和 Arguments 这三个元素,它们分别表示类、方法和方法参数,当 Objective-C 接收到这三个值后,就可以通过 runtime 唯一确定要调用的是哪个函数,然后调用这个函数。

    React native视图展示通过RCTRootView来展示,RCTRootView需要一个实例化的RCTBridge.

        rctBridge = [[RCTBridge alloc] initWithBundleURL:rnCodeLocation
                                              moduleProvider:nil
                                               launchOptions:launchOptions];
    

    RCTBridge是 Objective-C 与 JavaScript 交互的桥梁,后续的方法交互完全依赖于它,而整个初始化过程的最终目的其实也就是创建这个桥梁对象。

    RCTBridge通过初始化方法setUp来生成RCTBatchedBridge。

    RCTBatchedBridge 的作用是批量读取 JavaScript 对 Objective-C 的方法调用,同时它内部持有一个 JavaScriptExecutor,这个对象用来执行 JavaScript 代码。

      Class batchedBridgeClass = objc_lookUpClass("RCTBatchedBridge");
      Class cxxBridgeClass = objc_lookUpClass("RCTCxxBridge");
    
      Class implClass = nil;
    
      if ([self.delegate respondsToSelector:@selector(shouldBridgeUseCxxBridge:)]) {
        if ([self.delegate shouldBridgeUseCxxBridge:self]) {
          implClass = cxxBridgeClass;
        } else {
          implClass = batchedBridgeClass;
        }
      } else if (cxxBridgeClass != nil) {
        implClass = cxxBridgeClass;
      } else if (batchedBridgeClass != nil) {
        implClass = batchedBridgeClass;
      }
    

    BatchedBridge 的关键是 start 方法分为五个步骤:

    • 加载 JavaScript 源码
    • 加载所有不能被懒加载的native modules
    • 初始化RCTJSCExecutor 对象,执行JavaScript代码
    • 生成模块列表并写入 JavaScript 端
    • 执行 JavaScript 源码

    加载JavaScript源码

      __weak RCTBatchedBridge *weakSelf = self;
      __block NSData *sourceCode;
      [self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) {
        if (error) {
          RCTLogWarn(@"Failed to load source: %@", error);
          dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf stopLoadingWithError:error];
          });
        }
    
        sourceCode = source;
        dispatch_group_leave(initModulesAndLoadSource);
      } onProgress:^(RCTLoadingProgress *progressData) {
    #if RCT_DEV && __has_include("RCTDevLoadingView.h")
        RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
        [loadingView updateProgress:progressData];
    #endif
      }];
    

    加载native modules

    注册Module

    RCT_EXPORT_MODULE 可以通过RCTRegisterModule注册Module.

    @protocol RCTBridgeModule <NSObject>
    
    /**
     * Place this macro in your class implementation to automatically register
     * your module with the bridge when it loads. The optional js_name argument
     * will be used as the JS module name. If omitted, the JS module name will
     * match the Objective-C class name.
     */
    #define RCT_EXPORT_MODULE(js_name) \
    RCT_EXTERN void RCTRegisterModule(Class); \
    + (NSString *)moduleName { return @#js_name; } \
    + (void)load { RCTRegisterModule(self); }
    
    // Implemented by RCT_EXPORT_MODULE
    + (NSString *)moduleName;
    
    @optional
    
      NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
      NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
      NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
    
      // Set up moduleData for pre-initialized module instances
      RCT_PROFILE_BEGIN_EVENT(0, @"extraModules", nil);
      for (id<RCTBridgeModule> module in extraModules) {
        Class moduleClass = [module class];
        NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    
        if (RCT_DEBUG) {
          // Check for name collisions between preregistered modules
          RCTModuleData *moduleData = moduleDataByName[moduleName];
          if (moduleData) {
            RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
                        "name '%@', but name was already registered by class %@",
                        moduleClass, moduleName, moduleData.moduleClass);
            continue;
          }
        }
    
        // Instantiate moduleData container
        RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
                                                                           bridge:self];
        moduleDataByName[moduleName] = moduleData;
        [moduleClassesByID addObject:moduleClass];
        [moduleDataByID addObject:moduleData];
    
        // Set executor instance
        if (moduleClass == self.executorClass) {
          _javaScriptExecutor = (id<RCTJavaScriptExecutor>)module;
        }
      }
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
    
      // The executor is a bridge module, but we want it to be instantiated before
      // any other module has access to the bridge, in case they need the JS thread.
      // TODO: once we have more fine-grained control of init (t11106126) we can
      // probably just replace this with [self moduleForClass:self.executorClass]
      RCT_PROFILE_BEGIN_EVENT(0, @"JavaScriptExecutor", nil);
      if (!_javaScriptExecutor) {
        id<RCTJavaScriptExecutor> executorModule = [self.executorClass new];
        RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
                                                                           bridge:self];
        moduleDataByName[moduleData.name] = moduleData;
        [moduleClassesByID addObject:self.executorClass];
        [moduleDataByID addObject:moduleData];
    

    RCTModuleData 模型

    RCTModuleData 保存需要导出的常量和实例方法.

    /**
     * Returns the module methods. Note that this will gather the methods the first
     * time it is called and then memoize the results.
     */
    @property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods;
    
    /**
     * Returns the module's constants, if it exports any
     */
    @property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *exportedConstants;
    

    RCTModuleData暴露给 JavaScript 的方法需要用 RCT_EXPORT_METHOD 这个宏来标记,为函数名加上了 rct_export 前缀,再通过 runtime 获取类的函数列表,找出其中带有指定前缀的方法并放入数组中:

    - (NSArray<id<RCTBridgeMethod>> *)methods
    {
      if (!_methods) {
        NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
    
        if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
          [moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
        }
    
        unsigned int methodCount;
        Class cls = _moduleClass;
        while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
          Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
    
          for (unsigned int i = 0; i < methodCount; i++) {
            Method method = methods[i];
            SEL selector = method_getName(method);
            if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
              IMP imp = method_getImplementation(method);
              NSArray *entries =
                ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);
              id<RCTBridgeMethod> moduleMethod =
                [[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
                                                    JSMethodName:entries[0]
                                                          isSync:((NSNumber *)entries[2]).boolValue
                                                     moduleClass:_moduleClass];
    
              [moduleMethods addObject:moduleMethod];
            }
          }
    
          free(methods);
          cls = class_getSuperclass(cls);
        }
    
        _methods = [moduleMethods copy];
      }
      return _methods;
    }
    

    Objective-C 中Bridge 持有一个数组,数组中保存了所有的模块的 RCTModuleData 对象。只要给定 ModuleId 和 MethodId 就可以唯一确定要调用的方法。

    初始化JSExecutor

        dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
          // We're not waiting for this to complete to leave dispatch group, since
          // injectJSONConfiguration and executeSourceCode will schedule operations
          // on the same queue anyway.
          [performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig];
          [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
            [performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig];
            if (error) {
              RCTLogWarn(@"Failed to inject config: %@", error);
              dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf stopLoadingWithError:error];
              });
            }
          }];
          dispatch_group_leave(initModulesAndLoadSource);
        });
    
    - (void)setUp
    {
    #if RCT_PROFILE
    #ifndef __clang_analyzer__
      _bridge.flowIDMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
    #endif
      _bridge.flowIDMapLock = [NSLock new];
    
      for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(toggleProfilingFlag:)
                                                     name:event
                                                   object:nil];
      }
    #endif
    
      [self executeBlockOnJavaScriptQueue:^{
        if (!self.valid) {
          return;
        }
    
        JSGlobalContextRef contextRef = nullptr;
        JSContext *context = nil;
        if (self->_context) {
          context = self->_context.context;
          contextRef = context.JSGlobalContextRef;
        } else {
          if (self->_useCustomJSCLibrary) {
            JSC_configureJSCForIOS(true, RCTJSONStringify(@{
              @"StartSamplingProfilerOnInit": @(self->_bridge.devSettings.startSamplingProfilerOnLaunch)
            }, NULL).UTF8String);
          }
          contextRef = JSC_JSGlobalContextCreateInGroup(self->_useCustomJSCLibrary, nullptr, nullptr);
          context = [JSC_JSContext(contextRef) contextWithJSGlobalContextRef:contextRef];
          // We release the global context reference here to balance retainCount after JSGlobalContextCreateInGroup.
          // The global context _is not_ going to be released since the JSContext keeps the strong reference to it.
          JSC_JSGlobalContextRelease(contextRef);
          self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
          [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
                                                              object:context];
    
          installBasicSynchronousHooksOnContext(context);
        }
    
        RCTFBQuickPerformanceLoggerConfigureHooks(context.JSGlobalContextRef);
    
        __weak RCTJSCExecutor *weakSelf = self;
        context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
          RCTJSCExecutor *strongSelf = weakSelf;
          if (!strongSelf.valid) {
            return nil;
          }
    
          RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", @{ @"moduleName": moduleName });
          NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
          RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
          return RCTNullIfNil(result);
        };
    
        context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
          RCTJSCExecutor *strongSelf = weakSelf;
          if (!strongSelf.valid || !calls) {
            return;
          }
    
          RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil);
          [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
          RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call");
        };
    
        context[@"nativeCallSyncHook"] = ^id(NSUInteger module, NSUInteger method, NSArray *args) {
          RCTJSCExecutor *strongSelf = weakSelf;
          if (!strongSelf.valid) {
            return nil;
          }
    
          RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeCallSyncHook", nil);
          id result = [strongSelf->_bridge callNativeModule:module method:method params:args];
          RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
          return result;
        };
    
    #if RCT_PROFILE
        __weak RCTBridge *weakBridge = self->_bridge;
        context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
          if (RCTProfileIsProfiling()) {
            [weakBridge.flowIDMapLock lock];
            NSUInteger newCookie = _RCTProfileBeginFlowEvent();
            CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie);
            [weakBridge.flowIDMapLock unlock];
          }
        };
    
        context[@"nativeTraceEndAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
          if (RCTProfileIsProfiling()) {
            [weakBridge.flowIDMapLock lock];
            NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie);
            _RCTProfileEndFlowEvent(newCookie);
            CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie);
            [weakBridge.flowIDMapLock unlock];
          }
        };
    
        // Add toggles for JSC's sampling profiler, if the profiler is enabled
        if (JSC_JSSamplingProfilerEnabled(context.JSGlobalContextRef)) {
          // Mark this thread as the main JS thread before starting profiling.
          JSC_JSStartSamplingProfilingOnMainJSCThread(context.JSGlobalContextRef);
    
          __weak JSContext *weakContext = self->_context.context;
    
    #if __has_include("RCTDevMenu.h")
          // Allow to toggle the sampling profiler through RN's dev menu
          [self->_bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:@"Start / Stop JS Sampling Profiler" handler:^{
            RCTJSCExecutor *strongSelf = weakSelf;
            if (!strongSelf.valid || !weakContext) {
              return;
            }
            [weakSelf.bridge.devSettings toggleJSCSamplingProfiler];
          }]];
    #endif
    
          // Allow for the profiler to be poked from JS code as well
          // (see SamplingProfiler.js for an example of how it could be used with the JSCSamplingProfiler module).
          context[@"pokeSamplingProfiler"] = ^NSDictionary *() {
            if (!weakContext) {
              return @{};
            }
            JSGlobalContextRef ctx = weakContext.JSGlobalContextRef;
            JSValueRef result = JSC_JSPokeSamplingProfiler(ctx);
            return [[JSC_JSValue(ctx) valueWithJSValueRef:result inContext:weakContext] toObject];
          };
        }
    #endif
    
    #if RCT_DEV
        RCTInstallJSCProfiler(self->_bridge, context.JSGlobalContextRef);
    
        // Inject handler used by HMR
        context[@"nativeInjectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
          RCTJSCExecutor *strongSelf = weakSelf;
          if (!strongSelf.valid) {
            return;
          }
    
          JSGlobalContextRef ctx = strongSelf->_context.context.JSGlobalContextRef;
          JSStringRef execJSString = JSC_JSStringCreateWithUTF8CString(ctx, sourceCode.UTF8String);
          JSStringRef jsURL = JSC_JSStringCreateWithUTF8CString(ctx, sourceCodeURL.UTF8String);
          JSC_JSEvaluateScript(ctx, execJSString, NULL, jsURL, 0, NULL);
          JSC_JSStringRelease(ctx, jsURL);
          JSC_JSStringRelease(ctx, execJSString);
        };
    #endif
      }];
    }
    

    Native代码注册到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);
    }
    

    Objective-C 把 config 字符串设置成 JavaScript 中名为__fbBatchedBridgeConfig的全局变量。

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

    执行JavaScript代码

    - (void)enqueueApplicationScript:(NSData *)script
                                 url:(NSURL *)url
                          onComplete:(RCTJavaScriptCompleteBlock)onComplete
    {
      RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
    
      RCTProfileBeginFlowEvent();
      [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
        RCTProfileEndFlowEvent();
        RCTAssertJSThread();
    
        if (scriptLoadError) {
          onComplete(scriptLoadError);
          return;
        }
    
        RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"FetchApplicationScriptCallbacks", nil);
        [self->_javaScriptExecutor flushedQueue:^(id json, NSError *error)
         {
           RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,init");
           [self handleBuffer:json batchEnded:YES];
           onComplete(error);
         }];
      }];
    }
    

    Objective-C 与 JavaScript交互

    Objective-C 调用 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可以直接发送事件:

    /**
     * Send an event that does not relate to a specific view, e.g. a navigation
     * or data update notification.
     */
    - (void)sendEventWithName:(NSString *)name body:(id)body;
    

    JavaScript调用Native

    在调用 Objective-C 代码时,JavaScript 会解析出方法的 moduleID、methodID 和 params 并放入到 MessageQueue 中,等待 Objective-C 主动拿走,或者超时后主动发送给 Objective-C。

    - (id)callNativeModule:(NSUInteger)moduleID
                    method:(NSUInteger)methodID
                    params:(NSArray *)params
    {
      if (!_valid) {
        return nil;
      }
    
      RCTModuleData *moduleData = _moduleDataByID[moduleID];
      if (RCT_DEBUG && !moduleData) {
        RCTLogError(@"No module found for id '%zd'", moduleID);
        return nil;
      }
    
      id<RCTBridgeMethod> method = moduleData.methods[methodID];
      if (RCT_DEBUG && !method) {
        RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, moduleData.name);
        return nil;
      }
    
      @try {
        return [method invokeWithBridge:self module:moduleData.instance arguments:params];
      }
      @catch (NSException *exception) {
        // Pass on JS exceptions
        if ([exception.name hasPrefix:RCTFatalExceptionName]) {
          @throw exception;
        }
    
        NSString *message = [NSString stringWithFormat:
                             @"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
                             exception, method.JSMethodName, moduleData.name, params];
        RCTFatal(RCTErrorWithMessage(message));
        return nil;
      }
    }
    

    优势 / 劣势

    优势:

    1. 对于开发者而言,移动开发者有机会熟悉前端开发模式,前端能了解native开发流程;

    2. 能够利用 JavaScript 动态更新的特性,快速迭代。

    劣势:

    1.无法兼容Android和iOS两个版本,官方版本的组件中有Android和iOS组建的区分;

    2.不能做到完全屏蔽 iOS 端或 Android 的细节,前端开发者必须对原生平台有所了解。

    3.由于 Objective-C 与 JavaScript 之间切换存在固定的时间开销,所以性能必定不及原生。

    参考连接:

    https://bestswifter.com/react-native/

    相关文章

      网友评论

        本文标题:React native 基本介绍

        本文链接:https://www.haomeiwen.com/subject/kwociqtx.html