1.2RN의 아키텍처-과거와 현재
RN의 주요 스레드들과 Bridge를 사용한 과거 아키텍처, JSI를 사용하는 뉴 아키텍처
TL;DR
추억의 쪽지 시험
과거의 RN 스레드 구조 - Bridge 기반
React Native라는 이름에는 JavaScript(React)로 작성한 UI를 네이티브 플랫폼에서 렌더링한다는 철학이 담겨있다. 이 철학을 구현하기 위해 RN은 JavaScript 실행 환경과 네이티브 UI 환경을 오가야했는데, 그러기 위해서 RN은 여러 스레드를 병렬로 운용한다.
아래는 뉴 아키텍처라고 불리는 새로운 방식이 2022년 3월, 0.68버전과 함께 도입되기 전의 React Native 스레드 구조다.

이 Bridge 기반 레거시 아키텍처는 아래의 3개 스레드로 이루어져 있었다.
1. JS 스레드
JS 엔진(위의 이미지에서 JSC, JavaScript Core)에서 React Native로 쓴 코드를 실행한다. 즉, React Native 컴포넌트들을 렌더(React에선 컴포넌트 함수를 호출해 UI 트리를 계산하는 과정을 뜻한다)하고 상태를 관리하고 로직을 실행한다. React의 핵심인 컴포넌트 비교(Reconciliation)도 여기서 일어나지만, UI를 직접 조작하진 않는다. 이 작업은 UI 스레드의 역할이다.
터치나 스크롤 입력 처리 등의 네이티브 기능이 있으면 해당 기능은 Bridge를 통해 네이티브로 요청하지만, 그런 이벤트들의 콜백 처리(onPress, onScroll)는 JS 스레드의 몫이다.
JS 런타임은 싱글 스레드기 때문에 한번에 하나의 작업만 실행하고, 이 때문에 긴 작업(대형 JSON을 처리하거나 복잡한 로직을 다루거나)이 있으면 작업이 밀려, 애니메이션이 있다면 frame이 끊기는 frame drop이 날 수 있다.
2. UI 스레드 (메인 스레드)
실제로 화면을 그리는 네이티브 메인 스레드로, UI를 그리거나 변경하는 모든 일이 여기서 일어나기 때문에 UI 스레드로 불린다. 이외에도 터치/스크롤 입력 처리 등도 처리한다. 구체적인 용어는 iOS는 Main RunLoop, Android는 Main/UI thread이다.
UI 스레드가 즉시 반응을 못할 때, 즉 16.67ms(1초에 60장의 화면을 그리는 60fps에서 한 프레임을 그려야하는 제한 시간)안에 프레임을 못 그리면 화면이 끊기는 현상이 발생한다. 예를 들어, 큰 이미지를 한번에 띄우려고 하다보면 UI 스레드가 막혀 화면이 끊긴다.
3. Shadow 스레드
JS 스레드가 React Native 코드를 실행하면, React의 Reconciler는 "어떤 뷰를 만들고 싶다"라는 정보를 네이티브로 보냈는데, 네이티브에선 각 View의 정확한 위치나 크기(layout)를 계산하기 위해 Shadow 스레드와 통신했다. 여기에 있는 Yoga는 Facebook이 만든 C++로 작성된 Flexbox Layout 엔진으로, React처럼 RN도 CSS를 쓰지만 React와 같은 브라우저 엔진이 없어 스타일 속성을 계산하지 못하는 문제를 해결해준다.
계산이 끝나면 Shadow 스레드가 계산한 layout 결과는 UI 스레드로 전달되어 실제 네이티브 View 객체를 생성/갱신/삭제한다.
Bridge
Bridge는 한쪽 끝은 JS, 반대쪽 끝은 네이티브(iOS는 Objective-C, Android는 Java)로 짜여 있고, 그 사이를 비동기 배치(batch) 메시지로 오가도록 만든 통신층이다. 즉, 스레드가 아니라 JS 스레드와 네이티브 영역(UI 스레드 + 네이티브 모듈 워커 스레드들)을 연결하는 통신 경로다.
Bridge는 보통 아래와 같은 순서로 데이터 흐름을 담당한다.
- JS 스레드에서 네이티브 모듈을 써야하는 호출이 발생한다.
 - 브릿지의 JS 쪽에선 
MessageQueue라는 코드로 이 호출들을 배치 단위로 큐잉한다. - 프레임/이벤트 루프 틱에 맞춰 이 배치를 
flush하면, JS 런타임에 주입해둔 네이티브 함수(nativeFlushQueueImmediate)가 호출되어 네이티브 영역으로 진입한다. - 브릿지의 네이티브 쪽에선 직렬화된 호출 묶음을 역직렬화/해석해 해당 모듈/메소드를 실행한다. 모듈은 네이티브 모듈 전용 워커 큐에서, UI 변경은 UI 스레드에서 진행한다.
 - 네이티브의 결과나 콜백이 있으면 다시 브릿지로 배치를 전송한다.
 - 이 배치를 JS 콜백 큐가 받아 실행한다.
 
배치의 직렬화는 초창기에는 JSON-like 페이로드(배열의 배열)로 진행되었으나, 구현이 발전하면서 배열/바이너리 포맷 등으로 최적화되기도 하였다. 하지만 이 경우에도 배치 묶음으로, 비동기적으로, 메시지를 직렬화하여 왕복한다는 원리는 똑같았다.
이 방식은 JS로 크로스플랫폼 추상화를 하는데는 성공적이었으나, 호출마다 하나의 브릿지를 통해 직렬화/역직렬화를 하며 큐를 처리해야 했기에 왕복을 하는 과정에서 오버헤드와 지연이 발생했다. 예를 들어, JS에서 네이티브로 너무 많은 요청을 보내면 큐가 밀려서 UI가 끊기는 현상이 발생한다. 이러한 병목을 줄이기 위해 아래의 JSI 기반 뉴 아키텍처가 도입되었다.
New Architecture - JSI 기반

뉴 아키텍처는 Bridge 구조에서 생기는 이러한 문제들을 해결하기 위해 중요한 새로운 개념들을 도입했다.
1. JSI - JS로 네이티브 C++ 함수 실행하기
뉴 아키텍처는 JSI(JavaScript Interface)라는 인터페이스를 도입하는데, 이는 JavaScript 함수로 직접 네이티브 객체를 호출하고 접근하겠다는 것이다.
이를 위해 JSI는 JS 엔진(Hermes, JSC 등)의 런타임에 C++ 객체를 직접 노출해 네이티브 모듈을 동기적으로 호출할 수 있게 했다. 즉, 브릿지를 거쳐 가며 생겼던 직렬화/역직렬화 과정, 큐 지연, 콜백 id 관리 등의 병목을 제거해 JS에서 네이티브 모듈의 메소드를 호출하면 JS 런타임에 주입된 C++ 함수가 이를 바로 실행해주는 것이다.
2. Fabric - 뉴 아키텍처의 렌더러
우선, React와 RN에서 말하는 렌더러가 뭘까?
모던 React(함수형 컴포넌트 사용)에서 렌더(render)는 컴포넌트 함수를 호출해 다음 UI가 어떻게 생겼는지 계산하는 과정이다.
컴포넌트 함수의 본질은 props와 state를 받아 JSX를 반환하는 순수 계산이다. React Core의 Reconciler는 이 계산 결과를 바탕으로 어떤 요소가 바뀌는지를 판단한다. 이 판단 결과를 통해 실제 화면/뷰를 바꾸는 것을 Commit 단계라고 하는데, 이 Commit 단계에서 실제 DOM 노드나(React) 네이티브 뷰(RN)의 어디에 어떻게 변경할지 정의하는게 Renderer이다.

즉, Reconciler가 만든 레이아웃 변경 계획을 실제로 화면에 그릴 수 있도록 플랫폼에 적용해주는게 Renderer다. Bridge 기반 아키텍처의 렌더러인 Paper는 Reconciler에서 나온 변경 계획을 Bridge를 통해 네이티브로 전달했고, 네이티브는 앞서 언급했듯 Shadow Thread와 통신하며 View를 업데이트했다.
Fabric은 Reconciler가 수립한 레이아웃 변경 계획을 JSI를 통해 바로 C++ Shadow Tree로 갱신해 Yoga의 레이아웃 계산 → 커밋 → UI 스레드 적용을 할 수 있게 해주는 C++ 기반 렌더링 파이프라인이다.
Fabric을 통해 Reconciler의 결과는 C++로 만들어지고, Yoga의 Flexbox 레이아웃 계산, Commit, Mount가 모두 C++에서 일관적으로 처리되고 UI에 스레드에 적용되기에 Bridge를 통한 비동기/배치 직렬화 통신으로 인한 지연 및 프레임 드랍 위험을 해결할 수 있게 되었다.
3. TurboModules와 Codegen - 뉴 아키텍처의 네이티브 모듈 시스템
RN으로 개발 중 RN 자체 또는 라이브러리들에서도 제공하지 않는 네이티브 기능이 필요할 때가 있다. 이럴때는 물론 네이티브 플랫폼 언어로 필요한 부분에 대한 개발을 진행해야한다.
기존에 개발자들은 이를 위해 JS에서 브릿지로 요청을 보내기 위해 데이터를 직렬화해야했고, 네이티브 코드와 JS 코드를 직접 타입 안전하게 바인딩해주어야 했다. 또, 요청은 브릿지를 통해 배치 단위로 보내졌기 때문에 비동기적으로 이루어졌고 응답도 직렬화 가능한 값만 받을 수 있었으므로 불편했다.
뉴 아키텍처에선 TurboModules와 Codegen이 쌍으로 쓰여 네이티브 기능을 JS에 노출하는 방식을 바꾼다. TurboModules는 TypeScript 또는 Flow로 만들고자 하는 네이티브 모듈의 타입 스펙을 선언하게 해준다. Codegen은 이 스펙을 토대로 iOS/Android용 바인딩과 JSI 브릿지 레이어를 자동으로 생성한다. 이 방식으로 네이티브 API는 JSI를 통해 동기 호출이 가능해졌고, Codegen을 통해 타입 안전한 바인딩이 자동으로 생성되어 개발자들이 네이티브 API 개발을 보다 편하게 할 수 있게 되었다. 또, 네이티브의 HostObject를 참조하는 것도 가능해져 기존에 직렬화를 염두에 두어야 했던 값들 보다 더 큰 데이터를 유연하게 전달받을 수 있게 되었다.
이렇게 들으면 너무나도 좋고, 뉴 아키텍처를 마다할 이유가 없을것 같으나...
아직은 부족한 커뮤니티의 지원

인터넷을 검색해보면 글 작성 시점 현재(25.10.15) 뉴 아키텍처를 사용해본 사람들은 회의감을 느끼고 브릿지 아키텍처로 롤백하는 경우가 아주 많다. 그 이유는 reanimated 같은 필수적인 대형 라이브러리조차 뉴 아키텍처를 잘 지원하지 않아 앱의 사용성이 오히려 더 떨어진다는 것.
뉴 아키텍처는 React Native의 오래된 숙원 사업이었으며, 이론적으로 진정한 cross-platform으로 가기 위한 기반을 만든 것이라고 평가하는 분들도 있다. 따라서 개인적으로는 지금은 좀 시기상조일지라도 계속해서 RN을 다룬다면 도입하게 될 가능성이 높을 것으로 생각한다.