React Native New Architecture Guide

React Native Old/New Architecture의 개념과 project간의 지원성 요약

MJ Studio
MJ Studio

--

https://www.shutterstock.com/image-vector/large-dark-blue-bridge-against-backdrop-1840955977

TL;DR;

목차

1. JSI와 New Architecture에 대하여
2. Bridge, Bridgeless Mode
3. Interop Layer
4. App & Library Supporting Dimension
5. Supporting Table

이 글을 쓰는 시점엔 최신 React Native버전이 0.73.6이다.(추후 RN업데이트 시 이 글이 업데이트 될 수 있다.)

React Native New Architecture Working Group repo에서 주로 지식을 습득하여 정리된 것이다.

글에 틀린 부분이 있을 수도 있다.

이 글에서 Native Module은 mmkv같은 유틸리티 모듈, Native Component는 Native에서 UI를 그리는 컴포넌트를 지칭한다.

JSI(JavaScript Interface)와 New Architecture

RN에서 Bridge의 역할은 많은 자료가 있으므로 설명을 생략한다.

JSI는 JavaScript Interface의 준말이다.

JSI는 JavaScript과 C++의 동기적 바인딩이다. JS에서 C++의 함수나 객체를 동기적으로 호출하거나 참조할 수 있고 그 역도 같다.

JSI는 페이스북이 React Native에서 쓰기 위해 만든 하나의 라이브러리이다.

그래서 React Native의 JSI구현체는 어디있느냐? 했을때 여기에 있다.

여기서 add_library가 되어 jsi라는 라이브러리가 되고, ReactAndroid같은 모듈에서 target_link_libraries 로 참조된다.

JSI에서는 HostObject라는 개념이 중요한데, JS Runtime에 HostObject의 구현체가 등록이 된다.

이것을 써서 React Native JS(Hermes)와 C++간의 소통을 Bridge대신에 수행하고 그걸 Native 코드인 Java와 Objective-C에 전달한다.

New Architecture의 Codegen 문법에서도 HostObject라는 개념이 나오는데 이것을 의미한다.

단순히 get, set같은 함수들을 가지고있고 메타가 이것을 근본적으로 어떻게 구현했는지 알아볼 시간과 저 긴 C++ 코드를 읽을 동기가 당장은 없으니 넘어가도록 한다.

어쨌든 New Architecture는 내부적으로 JSI모듈을 쓰는 것으로 Native Module을 변경하는 프로젝트를 의미한다.

https://www.youtube.com/watch?v=52El0EUI6D0

그러면 성능적 이점과 비동기적 동작을 동기적으로 바꿀 수 있다는 사기적인 이점을 얻게 되는데, 이것이 New Architecture의 핵심이다.

이로 인해 concurrent rendering, excluding bridge json serialization, lazy native moudle initialization 등의 이점이 생기는데, 더 자세한건 공식 문서를 참고할 수 있다.

그러면 우리가 Bridge를 쓰는 Old Architecture앱이라면 JSI를 쓰고있지 않느냐?

대답은 No이다.

react-native-reanimatedreact-native-mmkv같은 라이브러리들은 이미 JSI를 써서 브릿지를 거치지않는 API의 형태로 이전하여 속도가 빠르다.

단, 여기서 그들이 한 작업은 굉장히 복잡하다. 왜냐면 Turbo Module이나 Fabric같은 경우 그냥 codegen으로 spec을 정의하고 JSI를 직접적으로 거의 쓸 일이 없을정도로 단순(?)하게 Library를 개발할 수 있지만 그들은 JSI문법을 직접 써가며 작업을 한 것이기 때문이다.

JSI를 쓰는 과정은 iOS라면 .mm 파일에서 C++문법이 호환되기 때문에 비교적 괜찮다고 할 수 있겠지만, Android라면 결국 JNI(Java Native Interface)를 따로 설정해주고 C++ layer와 소통을 해주어야 하기 때문에 까다롭다.

그래서 Turbo Module과 Fabric을 쓴것보다 비효율적으로 구현이 되었느냐? 그것도 아니다. mmkv의 author의 말에 따르면 이 모듈은 이미 JSI를 사용하고 Turbo Module보다 빠르게 동작한다고 한다.

더 빠르다? 라는 말이 어색하지만 아마 그의 뜻은 Turbo Module도 JSI를 쓰지만 다른 추가적인 초기화 과정이 필요해 이것만큼 최적화되지 않을 것이다. 라는 의미로 받아들이면 될 것 같다. 또한 다른 글에서 아직 JSI를 직접 쓰는것에서 지원되는 기능까지 지원이 제대로 안된다는 댓글도 보았다.

만약 JSI문법을 직접 써보고싶다면(왜인지 모르겠지만, 그리고 이제 Interop Layer를 쓰면 New Architecture를 지원하는 라이브러리를 만들 때도 JSI를 직접 쓸 필요가 거의 없다), 이 튜토리얼 이나 react-native-mmkv 라이브러리의 코드를 직접 뜯어보는 것도 좋을 것이다.

그럼 Old Architecture에서도 잘 하면 JSI를 써서 빠르게 모든 이점을 챙길 수 있는데 왜 New Architecture를 구분하느냐? 라는 질문을 할 수 있다.

그 이유는 그게 Designed by API이기 때문이라고 추측한다. 이렇게 명시적으로 구분해서 내부적인 코드를 관리하지 않으면 하위호환성이 Interop Layer를 추가하는것정도가 아닌 겉잡을 수 없이 커지기 때문일 것 같다.

Interop Layer

Introduction

Interop layer는 Bridge로 개발되어있는 NativeComponent들을 New Architecture에서 지원하기 위해 개발된 기술이다. 0.72부터 베타로 등장했다.

이 글 에서 묘사되듯이 Interop Layer는 단순히 한 가지로 특정되지는 않는다.

New Architecture의 최종 목적지인 Bridgeless는 그로 이전하기 위해 많은 물리적 시간과 비용이 소모된다고 커뮤니티에서 관측되었다. 이를 위해 interop layer라는 것이 도입될 수 밖에 없었던 것이다.

그런데 저 문서를 보면 이건 Fabric을 위해 개발된 것이라고 적혀있다. 그러면 Native Module은 버린걸까?

아니다. 댓글및 여러가지를 보면 보면 0.73부터 Native Module은 Bridgeless Mode에서도 Interop layer를 이용해 Turbo Module로써 동작하게 된다고 한다. 이는 0.74에서 더 견고해질 예정이다.

또한 0.74는 기본적으로 Bridgeless Mode이다. 0.73.6의 코드를 확인해본 결과 이미 bridgeless가 default인 것 같다.

하지만 글들에서는 앱에서 New Architecture를 활성화시킨 뒤에 Interop Layer에만 의존하면 안된다고 주의를 표한다. 이또한 아직 실험적 기능이며 어떤 예기치 못한 상황이 생길지 모르기 때문에 Discussion에선 라이브러리를 New Architecture를 지원하도록 업데이트 하는것이 결국 필요할 것이라고 한다.

In React Native 0.74, there are various Interop Layers enabled by default which allows many libraries to work without any changes — however, it’s not perfect and some libraries will need to be updated to account for its quirks.

앱과 라이브러리의 Interop 동작

당신의 앱이 Old Architecture를 쓴다고 하자. 그러면 Interop Layer란 것은 존재하지 않으며 당연히 고려의 대상이 아니다.

라이브러리가 Bridge로 구현이 되어있다고 하면 당연히 계속 쓸 수 있을 것이고, TurboModule과 Fabric으로 이전을 해버렸다면 쓸 수 없다.

하지만 라이브러리가 활발하거나 Maintainer가 사려깊으신 분이라면 여기서 나온 테크닉들을 이용해 Old/New Architecture모두에서 적절한 분기로 동작하게 만들어두었을 것이다.

React Navigation과 React Reanimated등 New Architecture에서도 쓸 수 있는 앱들을 못쓰게 되었나? 생각해보면 그렇지 않다는 것을 깨달을 것이다.

당신의 앱이 New Architecture를 쓴다고 하자. 그럼 Interop Layer는 Bridge로 구현된 Native Module과 Native Component가 동작하게 해줄 것이다.

하지만 이 때, Native Component같은 경우, react-native.config.js 에서 unstable_reactLegacyComponentNames 를 아직 New Architecture를 지원하지 않는 컴포넌트의 이름을 정확히 명시해주어야 한다.

이것이 0.72에 개시된 후로 정확히 어떻게 발전되었는지는 발견하지 못했다. 일단 FastImage로 테스트를 해보았을 땐 계속 문제가 발생해서 0.73.6 기준으로 제대로 동작하고 있는지 확인할 수 없었다.

하지만 최신 버전의 RN, 최소 0.73 버전 이상이여야 Interop Layer의 동작이 크게 어색하지 않음이 보장이 될 것이고 시간이 날때 프로젝트의 RN 버전을 업그레이드 하는것을 추천한다.

라이브러리가 TurboModule이나 Fabric으로 구현이 되었다면 당연히 그냥 쓸 수 있다.

내 생각엔 당신이 New Architecture로 Migration을 했다면 Native Component때문에 문제가 생길 일은 많지 않을 것이다. 커뮤니티는 상당히 빠르게 발전하고 이미 많이 사용되는 순서대로 몇백개의 라이브러리들이 모두 New Architecture를 지원하는 형태로 바뀌어가기에 문제가 없을 것이다.

여기서 New Architecture를 지원하는 형태란 Bridge형태를 유지하며 Bridgeless앱에서 Interop Layer가 잘 동작할 수 있게 내부 API를 변경해서 지원해주거나, 아니면 Fabric, Turbo Module로 바꿔버리거나 둘 중 하나이다. 전자는 비교적 간단하지만 후자는 react-native-video에 올라온 PR을 보면 알 수 있겠듯이 꽤나 복잡하다.

Maintain이 포기된 fast-image같은 라이브러리들도 커뮤니티가 활성화되어 fork된 프로젝트가 만들어지는 등, 좋은 움직임을 보여주고 있다.

Bridgeless vs Bridge in New Architecture

New Architecture를 켜도 Bridge모드Bridgeless모드 두 가지로 나뉜다.

Bridgeless는 그렇게 어려운 개념이 아니다. 말 그대로 Bridge를 없앤 구현체를 의미한다.

New Architecture의 Bridge모드는 말 그대로 아직 Bridge구현체를 남겨둔 설정이다.

Bridgeless는 0.73부터 도입되었고 아직 실험단계이다. Release Announcement

New Architecture를 활성화해도 Bridgeless모드를 disable하면 Bridge모드로 실행시킬 수 있다.

이것을 설정하는 코드는 안드로이드 기준으로 MainApplication.kt에 있으므로 load 함수의 정의를 보자.

기본값은 turbo module, fabric, bridgeless까지 모두 true이다.

그렇다. 지금 0.73.6으로 RN프로젝트를 만들고 New Architecture를 적용한다면 바로 Bridgeless모드를 사용할 수 있는 것이다.

아 파일을 조금 살펴보면 다음과 같은 코드가 있는데,

@VisibleForTesting
public fun isConfigurationValid(
turboModulesEnabled: Boolean,
fabricEnabled: Boolean,
bridgelessEnabled: Boolean
): Pair<Boolean, String> =
when {
fabricEnabled && !turboModulesEnabled ->
false to
"fabricEnabled=true requires turboModulesEnabled=true (is now false) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
bridgelessEnabled && (!turboModulesEnabled || !fabricEnabled) ->
false to
"bridgelessEnabled=true requires (turboModulesEnabled=true AND fabricEnabled=true) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
else -> true to ""
}

이는 Bridgeless를 사용하려면 항상 TurboModule과 Fabric을 켜줘야 한다는 것이고, Fabric또한 내부적으로 Turbo Module을 사용하는듯한(살펴보진 않았다.) 암시를 받을 수 있다.

혼동

그리고 보통 app project의 입장에서 Bridge Mode가 필요할 일은 많지 않다.

여기서 혼동하기 쉬운게, Interop Layer를 사용하기 위해 Bridgeless를 꺼서 Bridge Mode로 실행시켜야 되는것 아니냐? 할 수 있는데 그것이 아니다.

New Architecture App에서 사용되는 Interop Layer란건 Bridgeless를 위해 나온 개념이고 그대로 Bridge를 키고 이전 라이브러리들을 사용하려면 Bridge Mode를 쓰면 된다.

⚠️ 하지만 Old Architecture로 이전 라이브러리를 쓸 때와 New Architecture로 바꾸고 Bridge Mode로 이전 라이브러리를 쓸 때 정확히 모든것이 동일할 순 없으니 주의를 요한다.

Bridgeless에서도 Interop layer는 잘 작동하여 라이브러리를 쓰는데(라이브러리가 잘 지원하는 한) 문제가 없고 실제 Bridge API에 접근하는 코드를 사용하는 것이 아니라면 활성화시킬 필요가 없다.

보통 이런 코드들은 라이브러리내에 상주한다.

변경되는 Bridge API들은 이 곳에서 확인할 수 있다.

Bridgeless모드는 Bridge초기화 시간이 없어져 Turbo Module의 Lazy Initialization과 더불어 추가적으로 Startup 시간을 줄일 수 있다.

https://www.youtube.com/watch?v=52El0EUI6D0

더불어 New Architecture의 Bridge Mode는 앞서 언급한 것처럼 점점 그 의미를 퇴색할 것이 예정된 수순이므로 크게 고려하지 않아도 될 것 같다.

Conclusion — App & Library Architecture

App

App Project는 다음과 같은 종류로 분류된다.

  • Bridge (Old Architecture)
  • Bridge (New Architecture) (보통 0.73이전의 New Architecture를 활성화시킨 앱 프로젝트에 해당된다.)
  • Bridgeless (New Architecture)

이렇게 분류된 기준은 개발한 Library를 테스트하는 지침서에서 이렇게 안내되기 때문이다.

Disable Bridgeless in the New ArchitectureDisable New Architecture두 가지의 섹션을 따로 분류한 것을 보면 결국 테스트해야할 환경은 3가지라는 것이 된다.

On top of ensuring that your library works with the New Architecture (either you migrated it or it is going through the interop layers we are providing), it is important to check that the library works in other two scenarios:

- New Architecture, in Bridge mode
- Old Architecture.

하지만 이 부분을 보면 Bridge Mode는 크게 테스트 할 필요가 없다고 보여진다.

0.74부터 Bridgeless 모드가 Default이기 때문이라는데, 0.73.6에서도 이미 그렇다.

Libary

  • Bridge Module without Bridgeless Interop Support
  • Bridge Module with Bridgeless Interop Support
  • JSI Module(like react-native-mmkv)
  • New Architecture Module(Turbo Module, Fabric)
  • New Architecture Module(Turbo Module, Fabric) with old architecture support

일단 라이브러리 제작 가이드라인에선 아직 Turbo Module이나 Fabric으로 이전한 라이브러리가 아니라면 항상 Interop Layer을 우선지원하라고 한다.

Conclusion — Architecture Supporting Table

이제 표를 작성해보자.

아래의 테이블들은 아키텍처에 따른 상호동작관계를 정의한 것이며, RN 0.73.6의 기준에서 작성된 것이다.

Where to go?

다음 글은 실제 Turbo Module, Fabric의 라이브러리를 Codegen을 써서 구성하는 법에 대해서 정리를 해보고자 한다.

글을 읽어주셔서 감사합니다.

--

--