React native基本紹介

20610 ワード

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を使用していても、UIKIなどのフレームワークが必要で、Objective-Cコードが呼び出されます.JavaScriptはただ補助的で、両端UIを簡素化して繰り返し開発する仕事を提供して、Nativeにとってただ花を添えるだけで、雪の中で炭を送るのではありません.単純にReact nativeでnative端を放棄しようとするのは現実的ではない.
React nativeロードプロセス
JavaScript CoreはObjective-C向けのフレームワークで、Objective-CではJSCoextでオブジェクト、メソッドなど様々な情報を取得し、JavaScript関数の実行を呼び出すことができます.
React Nativeがこの問題を解決する案は、Objective-CとJavaScriptの両端にコンフィギュレーションテーブルを保存し、すべてのObjective-CがJavaScriptに露出するモジュールと方法をマークすることです.このように、どちらが他方のメソッドを呼び出すかにかかわらず、実際に伝達されるデータはModuleId、MethodId、Argumentsの3つの要素のみであり、クラス、メソッド、メソッドパラメータをそれぞれ表し、Objective-Cがこの3つの値を受信すると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メソッドが5つのステップに分かれていることです.
  • 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 
    
    /**
     * 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 *moduleClassesByID = [NSMutableArray new];
      NSMutableArray *moduleDataByID = [NSMutableArray new];
      NSMutableDictionary *moduleDataByName = [NSMutableDictionary new];
    
      // Set up moduleData for pre-initialized module instances
      RCT_PROFILE_BEGIN_EVENT(0, @"extraModules", nil);
      for (id 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)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 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> *methods;
    
    /**
     * Returns the module's constants, if it exports any
     */
    @property (nonatomic, copy, readonly) NSDictionary *exportedConstants;
    

    RCTModuleDataをJavaScriptに暴露する方法はRCT_が必要ですEXPORT_METHODというマクロをマークし、関数名にrct_を付けましたexport接頭辞、runtimeでクラスの関数リストを取得し、指定した接頭辞を持つメソッドを見つけて配列に入れます.
    - (NSArray> *)methods
    {
      if (!_methods) {
        NSMutableArray> *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 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が指定されている限り、呼び出すメソッドを一意に決定できます.
    JSEExecutorの初期化
        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 *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 *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 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;
      }
    }
    

    メリット/デメリット
    メリット:
  • 開発者にとって、モバイル開発者はフロントエンド開発モデルを熟知する機会があり、フロントエンドはnative開発プロセスを理解することができる.
  • JavaScriptの動的更新機能を利用して、高速反復が可能です.

  • 劣勢:
    1.AndroidとiOSの2つのバージョンを互換性がなく、公式バージョンのコンポーネントにはAndroidとiOSが構成されている.
    2.iOSまたはAndroidの詳細を完全に遮断することはできません.フロントエンドの開発者は、オリジナルのプラットフォームについて理解しなければなりません.
    3.Objective-CとJavaScriptの切り替えには一定の時間のオーバーヘッドがあるため、性能は必ずオリジナルに及ばない.
    リファレンス接続:
    https://bestswifter.com/react-native/