React Native Architecture

Table of contents

1. Legacy vs New Architecture 핵심 비교

구분 Legacy Bridge New Architecture
개발 기간 2015년 - 2025년 2018년 - 현재
안정화 2015년 2024년 10월 (RN 0.76)
통신 계층 JSON Bridge JSI (C++)
통신 방식 비동기 전용 동기/비동기
렌더러 Legacy Renderer Fabric
Native 모듈 Native Modules Turbo Modules
데이터 전달 JSON 직렬화 Direct Reference
타입 체크 런타임 Codegen (빌드 타임)
초기화 앱 시작 시 전체 지연 로딩 (Lazy)
메모리 효율 낮음 높음
C++ 지원 없음 완전 지원
디버깅 Chrome DevTools React Native DevTools

2. Legacy vs New Architecture 상세 비교

2.1 아키텍처 구조 비교

Legacy Bridge Architecture

┌─────────────────────────────────────────────┐
│         JavaScript Thread                   │
│  (React Components, Business Logic)         │
└──────────────────┬──────────────────────────┘
                   │
                   │ JSON 직렬화
                   ▼
            ┌──────────────┐
            │    Bridge    │  ← 병목 지점!
            │ (비동기 큐)   │
            └──────┬───────┘
                   │ JSON 역직렬화
                   ▼
┌──────────────────┴──────────────────────────┐
│         Native Threads                      │
├─────────────────┬───────────────────────────┤
│ Shadow Thread   │   Main Thread (UI)        │
│ (레이아웃 계산)  │   (네이티브 모듈 실행)    │
└─────────────────┴───────────────────────────┘

문제점:

  • ❌ 모든 통신이 Bridge를 경유 (Single Point of Failure)
  • ❌ JSON 직렬화/역직렬화 오버헤드
  • ❌ 비동기 통신만 가능 (동기 호출 불가)
  • ❌ 앱 시작 시 모든 Native Module 로드

New Architecture

┌─────────────────────────────────────────────┐
│         JavaScript Thread                   │
│       (Hermes Engine + JSI)                 │
└──────────────────┬──────────────────────────┘
                   │
                   │ JSI (C++ Direct Call)
                   ▼
            ┌──────────────┐
            │     JSI      │  ← No Bridge!
            │ (Direct Ref) │
            └──────┬───────┘
                   │
┌──────────────────┴──────────────────────────┐
│              C++ Layer                      │
├─────────────────┬───────────────────────────┤
│  Turbo Modules  │      Fabric Renderer      │
│  (Native Logic) │      (UI Components)      │
└─────────────────┴───────────────────────────┘

개선점:

  • ✅ Bridge 제거 → 직접 호출
  • ✅ Zero-copy 데이터 전달
  • ✅ 동기/비동기 모두 지원
  • ✅ 지연 로딩 (Lazy Initialization)

코드 참고:

2.2 통신 방식 비교

Legacy: Bridge를 통한 비동기 통신

// JavaScript
import { NativeModules } from "react-native";
const { LocationModule } = NativeModules;

// 1️⃣ 비동기만 가능
LocationModule.getCurrentPosition().then((position) => {
  // JSON 파싱 후 데이터 수신
  console.log(position.latitude, position.longitude);
});

// 2️⃣ 동기 호출 불가능
// const position = LocationModule.getCurrentPositionSync(); // ❌ 불가능!

// 3️⃣ 타입 안전성 없음
LocationModule.setSpeed("fast"); // 런타임 에러 가능

통신 과정:

JavaScript
    ↓ [1. Method Call]
    ↓ [2. JSON.stringify]
Bridge Queue
    ↓ [3. Queue에서 대기]
    ↓ [4. Native로 전송]
Native
    ↓ [5. JSON.parse]
    ↓ [6. 실제 함수 실행]
    ↓ [7. JSON.stringify]
Bridge Queue
    ↓ [8. JavaScript로 전송]
JavaScript
    ↓ [9. JSON.parse]
    ↓ [10. Callback 실행]

New Architecture: JSI를 통한 직접 호출

// JavaScript (TypeScript)
import LocationModule from "./specs/NativeLocationModule";

// 1️⃣ 동기 호출 가능
const position = LocationModule.getCurrentPositionSync();
console.log(position.latitude, position.longitude); // 즉시 실행

// 2️⃣ 비동기도 가능
const position2 = await LocationModule.getCurrentPosition();

// 3️⃣ 타입 안전성 보장
LocationModule.setSpeed(50); // ✅ number
// LocationModule.setSpeed('fast'); // ❌ TypeScript 컴파일 에러

통신 과정:

JavaScript
    ↓ [1. JSI Function Call]
C++ (JSI)
    ↓ [2. Direct Memory Access]
Native
    ↓ [3. 실제 함수 실행]
    ↓ [4. Direct Return]
JavaScript
    ↓ [5. 값 즉시 사용]

코드 참고:

2.3 Native Module 생성 비교

Legacy Native Module

JavaScript

// LocationModule.js
import { NativeModules } from "react-native";
const { LocationModule } = NativeModules;

export default LocationModule;

iOS (Objective-C / Swift)

Objective-C 방식 (전통적)

// RCTLocationModule.m
#import <React/RCTBridgeModule.h>
#import <CoreLocation/CoreLocation.h>

@interface RCTLocationModule : NSObject <RCTBridgeModule>
@end

@implementation RCTLocationModule

RCT_EXPORT_MODULE(LocationModule);

// 비동기만 가능
RCT_EXPORT_METHOD(getCurrentPosition:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  CLLocationManager *manager = [[CLLocationManager alloc] init];
  CLLocation *location = manager.location;

  // Dictionary로 변환 (JSON 직렬화를 위해)
  NSDictionary *result = @{
    @"latitude": @(location.coordinate.latitude),
    @"longitude": @(location.coordinate.longitude)
  };
  resolve(result);
}

@end

Swift 방식 (최신)

// LocationModule.swift
import Foundation
import CoreLocation

@objc(LocationModule)
class LocationModule: NSObject {

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }

  @objc
  func getCurrentPosition(
    _ resolve: @escaping RCTPromiseResolveBlock,
    rejecter reject: @escaping RCTPromiseRejectBlock
  ) {
    let manager = CLLocationManager()
    guard let location = manager.location else {
      reject("ERROR", "Location not available", nil)
      return
    }

    // Dictionary로 변환 (JSON 직렬화를 위해)
    let result: [String: Any] = [
      "latitude": location.coordinate.latitude,
      "longitude": location.coordinate.longitude
    ]
    resolve(result)
  }
}
// LocationModule.m (Bridge 파일 - Swift와 함께 필요)
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(LocationModule, NSObject)

RCT_EXTERN_METHOD(getCurrentPosition:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

@end

Android (Java / Kotlin)

Java 방식 (전통적)

// LocationModule.java
package com.myapp;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;

public class LocationModule extends ReactContextBaseJavaModule {
    LocationModule(ReactApplicationContext context) {
        super(context);
    }

    @Override
    public String getName() {
        return "LocationModule";
    }

    @ReactMethod
    public void getCurrentPosition(Promise promise) {
        // WritableMap으로 변환 (JSON 직렬화를 위해)
        WritableMap result = Arguments.createMap();
        result.putDouble("latitude", 37.5665);
        result.putDouble("longitude", 126.9780);
        promise.resolve(result);
    }
}

Kotlin 방식 (최신, 권장)

// LocationModule.kt
package com.myapp

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments

class LocationModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName() = "LocationModule"

    @ReactMethod
    fun getCurrentPosition(promise: Promise) {
        try {
            // WritableMap으로 변환 (JSON 직렬화를 위해)
            val result = Arguments.createMap().apply {
                putDouble("latitude", 37.5665)
                putDouble("longitude", 126.9780)
            }
            promise.resolve(result)
        } catch (e: Exception) {
            promise.reject("ERROR", e.message, e)
        }
    }
}

Turbo Module (New Architecture)

TypeScript Spec (Codegen)

// NativeLocationModule.ts
import type { TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";

export interface Spec extends TurboModule {
  // 타입 정의로 Native 코드 자동 생성
  getCurrentPositionSync(): {
    latitude: number;
    longitude: number;
  };

  getCurrentPosition(): Promise<{
    latitude: number;
    longitude: number;
  }>;

  setSpeed(speed: number): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>("LocationModule");

iOS - Objective-C++

// RCTLocationModule.mm
#import "RCTLocationModule.h"
#import <CoreLocation/CoreLocation.h>
#import "NativeLocationModuleSpec.h" // Codegen이 생성

@interface RCTLocationModule () <NativeLocationModuleSpec>
@end

@implementation RCTLocationModule

RCT_EXPORT_MODULE(LocationModule);

- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params {
  return std::make_shared<NativeLocationModuleSpecJSI>(params);
}

// 동기 함수 가능!
- (NSDictionary *)getCurrentPositionSync {
  CLLocationManager *manager = [[CLLocationManager alloc] init];
  CLLocation *location = manager.location;

  return @{
    @"latitude": @(location.coordinate.latitude),
    @"longitude": @(location.coordinate.longitude)
  };
}

- (void)getCurrentPosition:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject {
  resolve([self getCurrentPositionSync]);
}

- (void)setSpeed:(double)speed {
  // 구현
}

@end

iOS - Swift (최신, 권장) 🆕

// LocationModule.swift
import Foundation
import CoreLocation

@objc(LocationModule)
class LocationModule: NSObject, NativeLocationModuleSpec {

    // Turbo Module 등록
    @objc
    static func moduleName() -> String {
        return "LocationModule"
    }

    @objc
    static func requiresMainQueueSetup() -> Bool {
        return false
    }

    // 동기 함수 가능!
    @objc
    func getCurrentPositionSync() -> [String: Double] {
        let manager = CLLocationManager()
        guard let location = manager.location else {
            return ["latitude": 0.0, "longitude": 0.0]
        }

        return [
            "latitude": location.coordinate.latitude,
            "longitude": location.coordinate.longitude
        ]
    }

    @objc
    func getCurrentPosition(
        _ resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        let position = getCurrentPositionSync()
        resolve(position)
    }

    @objc
    func setSpeed(_ speed: Double) {
        // 구현
        print("Speed set to: \(speed)")
    }
}
// LocationModule.mm (Bridge 파일 - Swift Turbo Module용)
#import <React/RCTBridgeModule.h>
#import "NativeLocationModuleSpec.h"

@interface RCT_EXTERN_REMAP_MODULE(LocationModule, LocationModule, NSObject)

RCT_EXTERN_METHOD(getCurrentPositionSync)
RCT_EXTERN_METHOD(getCurrentPosition:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(setSpeed:(double)speed)

@end

Android - Kotlin (최신, 권장) 🆕

// LocationModule.kt
package com.myapp

import android.location.LocationManager
import android.content.Context
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments
import com.myapp.NativeLocationModuleSpec // Codegen이 생성

class LocationModule(reactContext: ReactApplicationContext) :
    NativeLocationModuleSpec(reactContext) {

    override fun getName() = "LocationModule"

    // 동기 함수 지원!
    @ReactMethod(isBlockingSynchronousMethod = true)
    override fun getCurrentPositionSync(): WritableMap {
        return Arguments.createMap().apply {
            putDouble("latitude", 37.5665)
            putDouble("longitude", 126.9780)
        }
    }

    @ReactMethod
    override fun getCurrentPosition(promise: Promise) {
        try {
            promise.resolve(getCurrentPositionSync())
        } catch (e: Exception) {
            promise.reject("ERROR", e.message, e)
        }
    }

    @ReactMethod
    override fun setSpeed(speed: Double) {
        // 구현
        println("Speed set to: $speed")
    }
}

Android - Java (호환성 유지용)

// LocationModule.java
package com.myapp;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.myapp.NativeLocationModuleSpec;

public class LocationModule extends NativeLocationModuleSpec {

    LocationModule(ReactApplicationContext context) {
        super(context);
    }

    @Override
    public String getName() {
        return "LocationModule";
    }

    // 동기 함수 지원!
    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getCurrentPositionSync() {
        WritableMap result = Arguments.createMap();
        result.putDouble("latitude", 37.5665);
        result.putDouble("longitude", 126.9780);
        return result;
    }

    @ReactMethod
    public void getCurrentPosition(Promise promise) {
        try {
            promise.resolve(getCurrentPositionSync());
        } catch (Exception e) {
            promise.reject("ERROR", e.getMessage(), e);
        }
    }

    @ReactMethod
    public void setSpeed(double speed) {
        // 구현
        System.out.println("Speed set to: " + speed);
    }
}

코드 참고:

2.4 렌더링 시스템 비교: Legacy Renderer vs Fabric

Legacy Renderer 동작 방식

┌─────────────────────────────────────────────┐
│     JavaScript Thread                       │
│  <View style={{width: 100}}> → Virtual DOM      │
└──────────────┬──────────────────────────────┘
               │ [1. JSON 직렬화]
               ▼
        ┌─────────────┐
        │   Bridge    │
        └──────┬──────┘
               │ [2. 메시지 큐]
               ▼
┌──────────────────────────────────────────────┐
│     Shadow Thread (Yoga Layout)             │
│  - Flexbox 계산                              │
│  - 위치/크기 결정                            │
└──────────────┬───────────────────────────────┘
               │ [3. JSON 직렬화]
               ▼
        ┌─────────────┐
        │   Bridge    │
        └──────┬──────┘
               │ [4. 메시지 큐]
               ▼
┌──────────────────────────────────────────────┐
│     Main Thread (Native UI)                 │
│  - UIView (iOS) / View (Android) 생성        │
│  - 실제 렌더링                               │
└──────────────────────────────────────────────┘

평균 렌더링 시간: 31ms (목표: 16ms/60fps)
→ 프레임 드롭 발생 ⚠️

문제점:

  • Bridge를 2번 통과 (JS → Shadow → Native)
  • 각 단계마다 JSON 직렬화/역직렬화
  • 우선순위 없음 (모든 업데이트 동일 처리)
  • 동기 레이아웃 측정 불가

Fabric Renderer 동작 방식

┌─────────────────────────────────────────────┐
│     JavaScript Thread (JSI)                 │
│  <View style={{width: 100}}> → React Tree        │
└──────────────┬──────────────────────────────┘
               │ [JSI Direct Call]
               ▼
┌──────────────────────────────────────────────┐
│     C++ Fabric Core                         │
│  ┌────────────────┐  ┌──────────────────┐   │
│  │ Shadow Tree    │  │ Priority Queue   │   │
│  │ (Yoga Layout)  │  │ (High/Low)       │   │
│  └────────┬───────┘  └──────────────────┘   │
└───────────┼──────────────────────────────────┘
            │ [Direct Memory Access]
            ▼
┌──────────────────────────────────────────────┐
│     Main Thread (Native UI)                 │
│  - 최소한의 변경만 적용                      │
│  - Batch Updates                            │
└──────────────────────────────────────────────┘

평균 렌더링 시간: 25ms
+ 우선순위 렌더링으로 체감 성능 ↑↑

개선점:

  • ✅ Bridge 제거 → JSI 직접 통신
  • ✅ 우선순위 기반 렌더링 (사용자 인터랙션 우선)
  • ✅ 동기 레이아웃 측정 가능
  • ✅ Concurrent Rendering 지원

코드 비교: 레이아웃 측정

Legacy: 비동기만 가능

import { UIManager, findNodeHandle } from "react-native";

const viewRef = useRef(null);

// 비동기 콜백 필요
UIManager.measure(findNodeHandle(viewRef.current), (x, y, width, height) => {
  console.log("Width:", width);
  // 여기서만 값 사용 가능
});

// 즉시 사용 불가 ❌
// const width = ???

Fabric: 동기 가능

import { useRef } from "react";

const viewRef = useRef(null);

// 동기로 즉시 측정
const layout = viewRef.current?.measureInWindow();
console.log("Width:", layout.width); // 즉시 사용 가능 ✅

// 또는 비동기도 가능
viewRef.current?.measureInWindow((x, y, width, height) => {
  console.log("Width:", width);
});

Fabric Component 예시

TypeScript Spec

// MyCardNativeComponent.ts
import type { ViewProps } from "react-native";
import type { HostComponent } from "react-native";
import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent";

interface NativeProps extends ViewProps {
  title: string;
  subtitle?: string;
  elevation: number;
  onCardPress?: () => void;
}

export default codegenNativeComponent<NativeProps>(
  "MyCard"
) as HostComponent<NativeProps>;

iOS 구현

// MyCardView.swift
import UIKit

@objc(MyCardView)
class MyCardView: UIView {

    @objc var title: NSString = "" {
        didSet { updateUI() }
    }

    @objc var subtitle: NSString = "" {
        didSet { updateUI() }
    }

    @objc var elevation: NSNumber = 0 {
        didSet {
            layer.shadowRadius = CGFloat(truncating: elevation)
            layer.shadowOpacity = 0.3
        }
    }

    @objc var onCardPress: RCTDirectEventBlock?

    private let titleLabel = UILabel()
    private let subtitleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }

    private func setupUI() {
        backgroundColor = .white
        layer.cornerRadius = 12

        addSubview(titleLabel)
        addSubview(subtitleLabel)

        let tapGesture = UITapGestureRecognizer(
            target: self,
            action: #selector(handleTap)
        )
        addGestureRecognizer(tapGesture)
    }

    private func updateUI() {
        titleLabel.text = title as String
        subtitleLabel.text = subtitle as String
    }

    @objc func handleTap() {
        onCardPress?([:])
    }
}

Android 구현

// MyCardView.kt
package com.myapp

import android.content.Context
import android.widget.LinearLayout
import android.widget.TextView
import com.facebook.react.bridge.Arguments
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.events.RCTEventEmitter

class MyCardView(context: Context) : LinearLayout(context) {

    private var reactContext: ThemedReactContext? = null
    private val titleView = TextView(context)
    private val subtitleView = TextView(context)

    var title: String = ""
        set(value) {
            field = value
            titleView.text = value
        }

    var subtitle: String = ""
        set(value) {
            field = value
            subtitleView.text = value
        }

    var elevation: Float = 0f
        set(value) {
            field = value
            this.elevation = value
        }

    init {
        orientation = VERTICAL
        addView(titleView)
        addView(subtitleView)

        setOnClickListener {
            reactContext?.getJSModule(RCTEventEmitter::class.java)
                ?.receiveEvent(id, "onCardPress", Arguments.createMap())
        }
    }
}

JavaScript 사용

import MyCard from './MyCardNativeComponent';

function App() {
  return (
    <MyCard
      title="Hello Fabric"
      subtitle="New Architecture"
      elevation={8}
      onCardPress={() => console.log('Card pressed!')}
      style={{ margin: 16 }}
    />
  );
}

코드 참고:


3. New Architecture 고급 기능

3.1 React Concurrent 기능 지원

New Architecture는 React의 Concurrent 기능을 완벽히 지원합니다.

Concurrent Features

import { useTransition, Suspense } from "react";

function App() {
  const [isPending, startTransition] = useTransition();
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = (text) => {
    // 낮은 우선순위 업데이트
    startTransition(() => {
      setSearchTerm(text);
    });
  };

  return (
    <View>
      <TextInput onChangeText={handleSearch} />
      {isPending && <ActivityIndicator />}
      <Suspense fallback={<Loading />}>
        <SearchResults query={searchTerm} />
      </Suspense>
    </View>
  );
}

Automatic Batching

// Legacy Architecture: 3번 렌더링
function handleClick() {
  setCount((c) => c + 1); // Re-render
  setFlag((f) => !f); // Re-render
  setText("Updated"); // Re-render
}

// New Architecture: 1번만 렌더링 (자동 배칭)
function handleClick() {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  setText("Updated");
  // → 한 번에 처리!
}

3.2 동기 레이아웃 측정

Legacy의 문제점

// Legacy: 깜빡임 발생
function Tooltip() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  return (
    <View
      onLayout={(e) => {
        // ❌ 비동기로 실행 → 화면에 먼저 그려진 후 위치 변경
        setPosition({
          x: e.nativeEvent.layout.x,
          y: e.nativeEvent.layout.y,
        });
      }}
    >
      <Text>Tooltip</Text>
    </View>
  );
}

New Architecture 해결

import { useLayoutEffect, useRef } from 'react';

function Tooltip() {
  const viewRef = useRef(null);
  const [position, setPosition] = useState({ x: 0, y: 0 });

  // ✅ 동기 실행 → 화면에 그리기 전에 위치 결정
  useLayoutEffect(() => {
    if (viewRef.current) {
      viewRef.current.measure((x, y, width, height) => {
        setPosition({ x, y });
      });
    }
  }, []);

  return (
    <View ref={viewRef} style={{ left: position.x, top: position.y }}>
      <Text>Tooltip</Text>
    </View>
  );
}

3.3 DOM Node APIs (React Native 0.82+)

React Native 0.82부터 DOM 스타일의 Node API를 지원합니다.

// 새로운 DOM 스타일 API
import { findNodeHandle } from "react-native";

function MyComponent() {
  const viewRef = useRef(null);

  const inspectNode = () => {
    const node = findNodeHandle(viewRef.current);

    // DOM 트리 탐색
    const parent = node.parentNode;
    const children = node.childNodes;
    const firstChild = node.firstChild;

    // 스타일 정보
    const styles = node.computedStyle;

    // 레이아웃 정보
    const bounds = node.getBoundingClientRect();
    console.log("Position:", bounds.x, bounds.y);
    console.log("Size:", bounds.width, bounds.height);
  };

  return <View ref={viewRef}>...</View>;
}

4. 참고 자료

4.1 공식 문서

New Architecture

JSI (JavaScript Interface)

Turbo Modules

Fabric Renderer

Codegen

4.2 커뮤니티 & 학습 리소스

Working Group

예제 프로젝트

라이브러리 호환성