React Native 프로젝트를 설정하고 빌드하는 과정에서 NOBRIDGE LOG가 눈에 띄었습니다. 이는 Bridgeless 모드와 관련된 내용이었으며, JavaScript와 네이티브 코드가 Bridge 없이 어떻게 통신하는지에 대한 궁금증이 생겨 이를 조사한 내용을 정리한 글입니다.
React Native 기존 아키텍처
기존 React Native 아키텍처는 주로 bridge라는 구조를 기반으로 합니다. 이 bridge는 JavaScript 코드와 native 코드 간의 통신을 담당하며, JavaScript에서 정의된 UI를 native 컴포넌트로 변환하는 역할을 합니다.
구성 요소
- JavaScript thread: JavaScript 코드가 실행되는 thread입니다. UI 업데이트와 비즈니스 로직을 처리하는 번들 코드를 담당합니다.
- Main / UI native thread: UI 컴포넌트를 렌더링하는 thread입니다.
- Shadow / background thread (Yoga): 레이아웃을 계산하는 thread입니다.
- bridge: JavaScript와 native 간의 메시지를 전달하는 비동기 통신 채널입니다.
React Native 새로운 아키텍처
새로운 아키텍처는 C++로 작성 되었으며, Codegen과 Fabric이라는 새로운 구성 요소를 도입하고, bridgeless 구조를 통해 bridge 없이 JavaScript와 native 레이어 간의 직접적인 통신을 가능하게 합니다.
구성 요소
1. JavaScript Interface (JSI)
JSI는 JavaScript Interface로, C++로 작성된 계층입니다. JSI는 모든 JavaScript 엔진에서 동작하며, 이를 통해 JavaScript와 native 간의 직접 통신이 가능합니다.
- React Native 코드는 Metro 번들러를 통해 번들로 묶이고 JSI로 전달됩니다.
- JSI는 TurboModules에 대한 참조를 제공하여, JavaScript가 native 모듈과 직접 동기식으로 통신할 수 있도록 합니다.
2. Turbo Modules
TurboModules는 C++로 작성되었으며, native와 JavaScript 간의 동기 및 비동기 통신을 지원합니다.
- 동적 로딩: TurboModules는 필요한 native 모듈만 로드하므로 앱의 초기 로드 시간이 단축됩니다.
- 성능 향상: bridge 없이 직접 통신이 가능해 JSON 직렬화/역직렬화 과정이 제거되고, 앱의 성능이 크게 향상됩니다.
3. Codegen
Codegen은 JavaScript와 native 간의 통신을 위한 강력한 타입 기반 계약을 생성하는 도구입니다.
- 타입 생성: JavaScript와 TypeScript의 동적 타입 특성을 보완하기 위해, Codegen은 C++과의 통신을 위한 타입 인터페이스를 생성합니다.
- 컴파일 시 최적화: 런타임 대신 빌드 타임에 인터페이스를 생성하여 실행 성능을 개선합니다.
4. Fabric
Fabric은 새로운 renderer로, 이전 UI 렌더링 방식에서 발생했던 성능 문제를 해결합니다.
- 불변 트리 구조: UI 상태를 불변 트리로 유지하여 안전한 thread 업데이트를 가능하게 합니다.
- 백그라운드 렌더링: 우선순위가 낮은 작업을 백그라운드에서 처리하면서 Main thread 차단을 방지합니다.
- 동기적 레이아웃 읽기: 여러 thread에서 레이아웃 정보를 동기적으로 읽을 수 있어, 효율적인 업데이트가 가능합니다.
비교: 기존 아키텍처 vs 새로운 아키텍처
다음 코드를 예제로 두 아키텍처의 실행 흐름을 비교해 보겠습니다.
import React from 'react';
import { Button, Alert } from 'react-native';
const App = () => {
const showAlert = () => {
Alert.alert('버튼이 클릭되었습니다!');
};
return <Button title="클릭하세요" onPress={showAlert} />;
};
export default App;
기존 아키텍처
- JavaScript 계층
onPress
이벤트가 발생하면, JavaScript thread에서 이벤트를 처리합니다.showAlert
함수가 호출되고, Alert.alert
가 실행됩니다.
- bridge를 통한 데이터 전송
Alert.alert
호출 시, JavaScript에서 JSON 형식으로 메시지를 생성하여 bridge로 전달합니다.- 이 JSON 메시지에는 native에서 처리할 데이터가 포함됩니다.
- native 계층
- native thread는 bridge를 통해 전달받은 JSON 메시지를 디코딩하고, 이를 native 알림 표시 메서드로 변환합니다.
- native 알림이 표시됩니다.
- 제약사항
- 비동기 처리: bridge는 JavaScript와 native 간의 데이 터를 비동기로 주고받기 때문에, 처리 지연이 발생할 수 있습니다.
- 성능 오버헤드: JSON 직렬화 및 역직렬화 과정에서 성능 비용이 발생합니다.
- Main / UI thread 차단 가능성: UI 업데이트나 이벤트 처리 중 native와의 통신이 지연되면 프레임 드롭 현상이 나타날 수 있습니다.
새로운 아키텍처 (Fabric 및 JSI 기반)
- JavaScript 계층
onPress
이벤트가 발생하면, JavaScript thread에서 이벤트를 처리합니다.showAlert
함수가 호출되고, JSI를 통해 native 모듈에 직접 요청이 전달됩니다.
- JSI를 통한 데이터 전달
- JSI는 native 모듈을 직접 참조하므로, 별도의 JSON 직렬화 과정이 필요 없습니다.
- JavaScript 호출이 native 코드로 바로 전달되며, 동기적 통신이 가능합니다.
- native 계층
- native thread는 전달받은 요청을 즉시 처리하여 알림을 표시합니다.
- Fabric renderer를 통해 UI 업데이트도 더 효율적으로 수행됩니다.
- 장점
- 동기적 통신: JavaScript와 native 간의 직접적인 통신이 가능하므로, 딜레이가 줄어듭니다.
- 향상된 성능: JSON 직렬화 과정이 제거되고, 필요한 native 모듈만 동적으로 로드하여 앱의 초기 로드 시간이 단축됩니다.
- Fabric을 통한 UI 최적화: UI 업데이트 시 낮은 우선순위 작업은 중단하고, 백그라운드에서 처리하여 Main thread 차단을 방지합니다.
코드 실행 흐름 비교
기존 아키텍처
Button
클릭 → onPress
이벤트 → showAlert
호출- JavaScript에서 JSON 생성 → bridge를 통해 native로 전달
- native에서 JSON 디코딩 → 알림 표시
새로운 아키텍처
Button
클릭 → onPress
이벤트 → showAlert
호출- JSI를 통해 native 모듈에 직접 요청 → 알림 표시