Skip to main content

React Native 0.75 - 레이아웃에서 퍼센트 값 지원, 새로운 아키텍처 안정화, 템플릿 및 init 업데이트 등

· 26 min read
Gabriel Donadel Dall'Agnol
Gabriel Donadel Dall'Agnol
Software Engineer at Expo
Siddharth Kulkarni
Siddharth Kulkarni
Software Engineer at Coinbase
Thibault Malbranche
Thibault Malbranche
Lead Mobile Engineer at Brigad
Blake Friedman
Blake Friedman
Software Engineer at Meta
Riccardo Cipolleschi
Riccardo Cipolleschi
Software Engineer at Meta
Nicola Corti
Nicola Corti
Software Engineer at Meta

오늘, React Native 0.75 버전을 출시하게 되어 기쁘게 생각합니다!

이번 릴리스에서는 % 값을 지원하는 Yoga 3.1, 새로운 아키텍처를 위한 여러 안정화 수정, 그리고 React Native 프레임워크 사용을 권장하는 내용을 포함한 여러 기능을 제공합니다.

주요 내용

주요 변경 사항

주요 기능

Yoga 3.1과 레이아웃 개선

React Native 0.74에서 Yoga 버전 3.0을 출시한 이후, 우리는 여러분의 애플리케이션을 위한 다양한 개선 사항과 새로운 레이아웃 기능을 계속해서 추가해 왔다. React Native 0.75는 Yoga 3.1을 포함하고 있으며, 새로운 기능에 대한 자세한 내용은 공식 Yoga 릴리스 블로그 포스트에서 확인할 수 있다.

특히 많은 요청을 받은 주요 기능 중 하나는 gapstranslation과 같은 다양한 위치에서 % 값을 지원하는 것이다.

info

이 기능들은 New Architecture에서만 사용할 수 있다. 이 기능을 사용하고 싶다면 New Architecture로 마이그레이션을 고려해 보길 바란다.

간격에 백분율 값 사용하기

0.75 버전부터 여기에서 설명한 gap, columnGap, rowGap 속성에 % 값을 가진 문자열을 지원한다.

예를 들어:

function App(): React.JSX.Element {
return (
<SafeAreaView
style={{
marginTop: 20,
alignItems: 'center',
flex: 1,
rowGap: '20%',
}}>
<View
style={{flex: 1, flexDirection: 'row', columnGap: '10%'}}>
<View
style={{
backgroundColor: 'purple',
width: 100,
height: 100,
}}
/>
<View
style={{
backgroundColor: 'blue',
width: 100,
height: 100,
}}
/>
<View
style={{
backgroundColor: 'green',
width: 100,
height: 100,
}}
/>
</View>
<View
style={{flex: 1, flexDirection: 'row', columnGap: '10%'}}>
<View
style={{
backgroundColor: 'lime',
width: 100,
height: 100,
}}
/>
<View
style={{
backgroundColor: 'yellow',
width: 100,
height: 100,
}}
/>
<View
style={{
backgroundColor: 'orange',
width: 100,
height: 100,
}}
/>
</View>
<View
style={{flex: 1, flexDirection: 'row', columnGap: '10%'}}>
<View
style={{
backgroundColor: 'red',
width: 100,
height: 100,
}}
/>
<View
style={{
backgroundColor: 'violet',
width: 100,
height: 100,
}}
/>
<View
style={{
backgroundColor: 'magenta',
width: 100,
height: 100,
}}
/>
</View>
</SafeAreaView>
);
}

위 코드는 다음과 같이 렌더링된다:

AndroidiOS
Android GapsiOS Gaps

퍼센트 값 변환

transform prop은 이제 translate 변환에 % 값을 사용할 수 있다.

예를 들어, 다음 컴포넌트는 빨간색 사각형의 X 좌표를 너비의 100%만큼, Y 좌표를 높이의 100%만큼 이동시킨다:

function Translated() {
return (
<SafeAreaView
style={{
marginTop: 20,
flex: 1,
rowGap: '20%',
}}>
<View
style={{
backgroundColor: 'red',
width: 100,
height: 100,
transform: [{translateY: '100%'}, {translateX: '100%'}],
}}
/>
</SafeAreaView>
);
}

이 코드는 다음과 같이 렌더링된다:

AndroidiOS
Android TranslationiOS Translation

새로운 아키텍처 안정화 작업

React Conf에서 새로운 아키텍처가 베타 단계에 진입했음을 발표한 이후, 우리는 여러 버그 수정과 안정성 개선 작업을 진행했다.

우리의 목표는 새로운 아키텍처가 가까운 미래에 안정적으로 간주되는 것이다. 따라서 지난 몇 달 동안 우리는 기존 아키텍처와 새로운 아키텍처 간의 차이를 좁히는 데 집중했다. 수정한 버그와 추가된 기능의 예는 다음과 같다:

  • Android에서 adjustsFontSizeToFit 문제 수정 (#44075)
  • Android에서 textAlign이 인라인 뷰와 함께 작동하지 않는 문제 수정 (#44146)
  • iOS에서 텍스트 베이스라인이 위로 이동하는 문제 수정 (#44932)

Expo 팀과 함께 React Native 디렉토리에서 새로운 아키텍처 지원 정보를 추가하는 작업도 진행했다. 이를 통해 특정 라이브러리가 새로운 아키텍처를 이미 지원하는지 여부를 즉시 확인할 수 있다:

React Native 디렉토리

또한, 새로운 아키텍처 설문조사에 참여할 것을 권장한다. 이 설문조사는 새로운 아키텍처 롤아웃의 다음 단계를 결정하는 데 중요한 피드백을 수집하는 데 핵심적인 역할을 한다.

새로운 아키텍처 작업 그룹에서 발표한 새로운 아키텍처에서 UIManager 지원에 대한 포스트도 공유하고 싶다. 이 포스트는 Android에서 UIManager API의 개요를 제공하며, 더 복잡한 앱과 라이브러리의 마이그레이션을 어떻게 도울 수 있는지 설명한다.

이번 릴리스에는 jsi::Runtime에 접근하는 새로운 API도 포함되어 있다. 이 API는 이제 권장되는 방식으로 사용된다.

TurboModules에서 jsi::Runtime 접근하기

이전에는 네이티브 모듈에서 jsi::Runtime에 접근할 수 있는 권장 방법이 없었고, 사용자들은 위험한 방법으로 프레임워크를 우회해 접근했다. 0.74 버전에서는 jsi::Runtime에 안전하게 접근할 수 있는 실험적 API를 도입했고, 이제 0.75 버전에서 이 API가 안정화되었다는 소식을 전한다.

jsi::Runtime 접근 방법 예제

iOS에서는 Turbo Native Module이 RCTTurboModuleWithJSIBindings 프로토콜을 준수하게 만들 수 있다. 이제 installJSIBindingsWithRuntime을 구현해 스레드 안전한 방식으로 런타임에 접근할 수 있다.

@interface RCTSampleTurboModule () <RCTTurboModuleWithJSIBindings>
@end

#pragma mark - RCTTurboModuleWithJSIBindings
- (void)installJSIBindingsWithRuntime:(jsi::Runtime &)runtime {
runtime.global().setProperty(
runtime,
"myGlobalFunction",
jsi::Function::createFromHostFunction(...));
}

Android에서는 Turbo Native Module이 TurboModuleWithBindings 인터페이스를 준수하게 만들 수 있다. 이제 JNI 메서드인 getBindingsInstaller를 구현해 C++에서 스레드 안전한 방식으로 런타임에 접근할 수 있다.

public class SampleTurboModule extends NativeSampleTurboModuleSpec implements TurboModuleWithJSIBindings

@Override
public native BindingsInstallerHolder getBindingsInstaller();
// C++
jni::local_ref<BindingsInstallerHolder::javaobject> SampleTurboModuleJSIBindings::getBindingsInstaller(jni::alias_ref<jni::object> jobj) {
return BindingsInstallerHolder::newObjectCxxArgs(
[](jsi::Runtime& runtime) {
runtime.global().setProperty(
runtime,
"myGlobalFunction",
jsi::Function::createFromHostFunction(...));
}
);
}

UI 스레드에서 런타임에 접근해야 한다면, 새로운 API인 CallInvoker를 사용할 수 있다. 이 API는 단일 메서드인 invokeAsync를 제공하며, 이 메서드는 JS 스레드로 이동해 JS 런타임을 사용해 작업을 안전하게 실행한다. 이 API는 앞으로도 호환성을 유지할 것이다.

iOS에서는 RCTCallInvokerModule 프로토콜을 제공한다. 이 프로토콜을 준수하면 인프라가 모듈에 CallInvoker 접근 권한을 부여한다.

@interface RCTSampleTurboModule() <RCTCallInvokerModule>

[self.callInvoker callInvoker].invokeAsync([&](jsi::Runtime& runtime) {
// JS 스레드에서 작업 수행
}

Android에서는 CallInvokerReactContext를 통해 접근할 수 있으며, JNI 래퍼인 CallInvokerHolder를 통해 invokeAsync를 호출할 수 있다.

// Java
public abstract CallInvokerHolder getJSCallInvokerHolder();
// C++
jsCallInvokerHolder->cthis()->getCallInvoker()->invokeAsync([&](jsi::Runtime& rt) {
// JS 스레드에서 작업 수행
});

프레임워크 사용하기

올해 초 React Conf에서 공유한 바와 같이, 이제 React Native 앱을 개발할 때는 Expo와 같은 프레임워크를 사용하는 것이 권장된다.

이에 대한 자세한 내용은 이전 블로그 포스트 "React Native 앱 개발에 프레임워크 사용하기"에서 확인할 수 있다.

새로운 React Native 사용자들이 성공적으로 시작할 수 있도록 돕고자 한다. 프레임워크를 사용하면 최대한 생산성을 높일 수 있고, 새로운 앱을 개발할 때 최고의 개발자 경험을 제공할 수 있다고 믿는다.

이러한 권장 사항을 반영하기 위해 이번 버전에서는 다음과 같은 변경 사항을 적용했다:

  • /template 폴더를 react-native 패키지에서 별도의 저장소인 react-native-community/template로 이동했다.
  • 2024년 12월 31일부터 react-native init 명령어를 단계적으로 중단한다.

이미 Expo와 같은 프레임워크를 사용 중이라면 이 변경 사항이 전혀 영향을 미치지 않는다. React Native 0.75를 Expo SDK 51과 함께 사용할 수 있다. (자세한 방법은 이 Expo 포스트에서 확인할 수 있다.)

프레임워크를 사용하지 않거나 자신만의 프레임워크를 구축 중이라면, 이 변경 사항이 어떤 영향을 미치는지 살펴보자.

템플릿을 react-native-community/template로 이동

과거에는 react-native가 NPM 패키지 내부에 /template 폴더를 포함하고 있었고, 이는 Community CLI가 새로운 프로젝트를 생성할 때 사용되었다. 이 방식은 템플릿을 업데이트할 때마다 새로운 React Native 릴리스가 필요했기 때문에 업데이트 속도가 느렸다.

최근 프레임워크 사용을 권장하면서, 코어 NPM 패키지 내부에 특정 의견이 반영된 템플릿을 포함하는 것이 우리의 비전과 맞지 않다고 판단했다.

따라서 템플릿을 @react-native-community/template 패키지로 이동하기로 결정했다.

이 변경으로 커뮤니티가 템플릿을 유지하고 발전시키기 쉬워졌으며, 모든 변경 사항에 대해 React Native 릴리스에 의존할 필요가 없어졌다. 또한, 이 변경은 템플릿을 Community CLI에 더 가깝게 만들어 누구나 템플릿을 별도의 패키지로 쉽게 검토하고 의존할 수 있게 한다.

이 변경은 Community CLI를 사용해 새 프로젝트를 생성하는 사용자에게 완전히 투명하게 적용된다. 이제부터는 템플릿과 관련된 새로운 이슈는 템플릿 이슈 트래커에 보고해야 한다. upgrade-helper와 같이 템플릿에 의존하는 다양한 도구들도 이에 맞게 업데이트되었으며, 기존과 동일하게 작동할 것이다.

react-native init 단계적 폐지

템플릿과 마찬가지로, react-native init 명령어도 새로운 권장 사항에 맞게 조정되었다.

과거에는 react-native init이 새로운 React Native 프로젝트를 생성하는 기본 명령어였다. 그러나 2024년 현재, 이 명령어는 프레임워크가 제공하는 온보딩 경험과 동일한 수준을 제공하지 못한다. 따라서 더 이상 이 명령어를 사용하지 않고, 대신 Expo와 같은 프레임워크를 사용할 것을 권장한다.

현재는 Community CLI와 템플릿을 사용해 react-native init으로 새로운 프로젝트를 생성할 수 있지만, 다음과 같은 경고 메시지가 표시된다:

Init Deprecation

2024년 12월 31일부터는 init 명령어로 더 이상 프로젝트를 생성할 수 없다. 대신 다음 중 하나를 선택해야 한다:

  • Expo와 같은 프레임워크를 사용해 npx create-expo-app와 같은 전용 명령어로 새 프로젝트를 생성한다.
  • npx @react-native-community/cli init을 통해 Community CLI를 직접 호출한다.

react-native configinit을 제외한 다른 모든 명령어는 기존과 동일하게 작동한다는 점을 유의한다.

info

보다 원활한 마이그레이션을 위해, react-native@0.75.0 패키지는 여전히 @react-native-community/cli에 의존하고 있다. 하지만 가까운 미래에 이 의존성을 제거할 계획이다.

자동 링크 성능 개선

init 커맨드를 업데이트하는 작업을 진행하면서, 자동 링크 로직을 더 효율적으로 재작성했다. 이로 인해 Android와 iOS 모두에서 빌드 속도가 빨라졌다.

React Native 0.75 버전부터 Expo를 사용한다면, Android에서는 자동 링크 단계가 약 6.5배, iOS에서는 약 1.5배 빨라졌다. 이러한 개선 사항에 대해 더 자세히 알아보려면 여기를 참고한다.

주요 변경 사항

이번 섹션은 다소 길게 느껴질 수 있지만, 여기서 소개하는 주요 변경 사항은 React Native를 고급 방식으로 사용하는 소수의 사용자에게 주로 영향을 미칠 것으로 예상된다.

그럼에도 불구하고 완전성을 위해 그리고 참고 자료로서 이 내용을 여기에 제시한다.

Touchables를 TypeScript에서 Generic 표현식의 타입으로 사용할 수 없음

TouchableOpacityTouchableHighlight 컴포넌트가 함수형 컴포넌트로 전환되었다. 이제 이들은 value & type으로 사용할 수 없다. 예를 들어, 다음과 같은 코드는 더 이상 유효하지 않다:

import {TouchableHighlight} from 'react-native';
const ref = useRef<TouchableHighlight>();
// ^^^ TS2749: TouchableHighlight는 값을 참조하지만, 여기서는 타입으로 사용되고 있습니다.
// typeof TouchableHighlight를 사용하려고 한 건 아닌가요?

대신, React의 내장 타입인 React.ElementRef를 사용하거나, View 타입을 사용해야 한다:

import {TouchableHighlight} from 'react-native';
const ref1 = useRef<React.ElementRef<typeof TouchableHighlight>>();
// 또는
const ref2 = useRef<View>();

minSdk 23과 minIOSVersion 13.4를 지원하는 마지막 버전

React Native 0.75는 minSdk 23(Android 6.0)과 minIOSVersion 13.4를 지원하는 마지막 버전이다. 이 변경 사항은 0.75 버전 자체에서의 주요 변경은 아니지만, 앞으로의 계획을 공유하기 위해 알린다.

React Native 0.76부터는 minSdk 버전이 24(Android 7.0)로, minIOSVersion은 15.1로 업데이트된다.

이에 대한 자세한 내용은 공식 발표를 참고할 수 있다. Android 관련 발표iOS 관련 발표에서 확인할 수 있다.

Android: JSIModule 삭제 안내

com.facebook.react.bridge.JSIModule(소스)는 네이티브 모듈이 Android에서 JSI에 직접 접근할 수 있도록 임시로 도입한 API였다. 이 API의 접근자들은 0.74 버전에서 더 이상 사용되지 않음(deprecated)으로 표시되었으며, 오픈소스에서 이 API의 유의미한 사용 사례가 없음을 확인한 후 0.75 버전에서 완전히 삭제하기로 결정했다. 대신 Turbo Native Modules를 사용할 것을 권장한다.

Android: PopUp Menu를 별도의 패키지로 이동

0.74 버전에서 Android의 PopUpMenu@react-native 스코프 아래 별도의 패키지로 이동했다. 0.75 버전에서는 코어에 남아 있던 메서드를 모두 제거한다:

  • UIManagerModule.showPopupMenu()
  • UIManagerModule.dismissPopupMenu()

대안으로 @react-native/popup-menu-android 패키지에 있는 <PopupMenuAndroid /> 컴포넌트를 사용한다.

iOS: PushNotificationIOS 폐기 작업 완료

0.74 버전에서 PushNotificationIOS 모듈의 일부 API를 폐기했다.

0.75 버전에서는 이 API를 삭제해, 알림 메타데이터의 레거시 표현 방식을 제거했다.

삭제된 API는 다음과 같다:

  + (void)didReceiveLocalNotification:(UILocalNotification *)notification;
+ (void)didReceiveRemoteNotification:(NSDictionary *)notification;

대신 didReceiveNotification:(UNNotification *)notification를 사용한다.

커뮤니티 CLI: ram-bundle 및 profile-hermes 커맨드 제거

커뮤니티 CLI에서 두 가지 주요 커맨드인 ram-bundleprofile-hermes를 제거한다는 소식을 전한다.

ram-bundle 커맨드는 React Native 0.59에서 도입되어 메모리에 직접 번들을 로드해 실행할 수 있게 했다. 이 기능은 이제 기본 JS 엔진인 Hermes로 대체되었다. 따라서 ram-bundle 커맨드는 더 이상 사용하지 말아야 한다.

profile-hermes 커맨드는 JavaScript 코드의 CPU 성능을 프로파일링하는 도구였다. 이 커맨드는 구식 .cpuprofile 포맷을 사용했는데, 최신 Chrome 버전에서는 더 이상 지원하지 않는다. 또한, 디버깅 도구의 품질을 높이기 위해 이 기능을 독립적인 커맨드로 제공하는 방식을 점차 중단하고 있다. 이제 CPU 프로파일링은 실험적 새 디버거의 "Profiler" 패널에서 접근할 수 있다(단, Chrome에서 Hermes에 연결할 때는 사용할 수 없음).

주요 변경 사항

일반

  • 코드 생성

    • 순수 C++ TurboModules에서 생성된 클래스와 구조체의 이름이 약간 변경되었다. 이름에서 Cxx 토큰을 제거했다.
    • 부동소수점 열거형은 정밀도 오류 가능성으로 인해 더 이상 지원되지 않는다.
    • 네이티브에서 non-nullable 인자에 JS에서 null을 전달할 경우 에러를 발생시킨다.
  • 린팅

    • ESLint 설정에서 린팅 시 더 이상 prettier를 실행하지 않는다.
  • C++

    • ScrollViewShadowNode 생성자에 새로운 bool includeTransform 매개변수가 추가되었다.
    • RuntimeExecutor에서 executeAsynchronouslyexecuteSynchronously_CAN_DEADLOCK 메서드가 제거되었다.
    • JsErrorHandler.h에서 JsErrorHandlingFuncOnJsError로 이름을 변경했다.
    • handleFatalError.h에서 handleJsErrorOnJsError로 이름을 변경했다.
    • ReactPrimitives.h에서 사용되지 않는 import를 제거했다.
    • LongLivedObjectCollectionLongLivedObject의 get 메서드가 이제 Runtime 매개변수를 받는다.
    • utils/jsi.h 파일 이름을 jsi-utils.h로 변경했다.
  • TextInput

    • 더 이상 사용되지 않는 onTextInput 콜백을 제거했다.
  • Pressability

    • onLongPressShouldCancelPress_DEPRECATED, onResponderTerminationRequest_DEPRECATED, onStartShouldSetResponder_DEPRECATED 메서드를 제거했다.

Android

  • ReactViewBackgroundDrawable

    • CSSBackgroundDrawable을 대신 사용한다. 이 변경으로 인해 ReactViewBackgroundDrawableColorUtil에서 일부 API가 제거된다.
  • ReactContext

    • ReactContextReactApplicationContext가 이제 추상 클래스가 된다. 대신 BridgeReactContextBridgelessReactContext를 사용한다.
    • ReactContext.initializeWithInstance()를 삭제한다. 대신 BridgeReactInstance를 사용한다.
    • BridgelessReactContext.getJavaScriptContextHolder()를 제거한다. 대신 BridgelessCatalystInstance를 사용한다.
    • ReactContext.getRuntimeExecutor()를 제거한다. 대신 BridgelessCatalystInstance를 사용한다.
  • Layout

    • 퍼센트 단위의 플렉스 갭 값을 지원한다. 이로 인해 setGap, setRowGap, setColumnGap과 같은 메서드의 매개변수가 float에서 dynamic으로 변경된다.
    • Android Manifest에서 supportsRTL을 요구한다.
  • Runtime

    • ReactHostImpl에서 ReactJsExceptionHandler를 제거한다.
    • 기본 템플릿을 사용하지 않을 경우 앱이 코어 turbomodules를 반환하도록 한다.
  • DevSupport

    • DevSupportManagerFactory.create()가 새로운 PausedInDebuggerOverlayManager 매개변수를 받도록 변경한다.
  • Measurement

    • UIManagerModule.measureLayoutRelativeToParent()를 삭제한다.

iOS

  • 런타임
    • [RCTHost getSurfacePresenter][RCTHost getModuleRegistry]를 제거한다
    • EventPriority 클래스를 제거하고 기본값인 EventPriority::AsynchronousBatched를 항상 사용한다. 빌드가 실패할 경우 EventPriority 사용을 모두 제거한다
  • 이미지
    • 사용되지 않는 RCTImageLoadingPerfInstrumentationEnabled를 제거한다
  • 에러 처리
    • RCTBridge를 통해 RCTRedBox에 접근하는 방식을 제거한다
  • CocoaPods
    • BUILD_FROM_SOURCERCT_BUILD_HERMES_FROM_SOURCE로 이름을 변경한다
    • use_frameworks 및 Swift와의 호환성을 위해 React-CodegenReactCodegen으로 이름을 변경한다
  • 텍스트 입력
    • 더 이상 사용되지 않는 onTextInput 콜백을 제거한다

감사의 말

React Native 0.75 버전은 165명의 기여자1491개의 커밋을 통해 만들어졌다. 모든 분들의 노고에 감사드린다.

이번 릴리스 포스트에서 기능 문서화 작업에 참여한 추가 저자들에게도 감사드린다:

0.75 버전으로 업그레이드

기존 프로젝트를 React Native 버전 간 코드 변경 사항을 확인하려면 React Native Upgrade Helper를 사용하세요. 또한 업그레이드 문서를 참고하세요.

새 프로젝트를 생성하려면 다음 명령어를 사용하세요:

npx @react-native-community/cli@latest init MyProject --version latest

Expo를 사용한다면, React Native 0.75는 Expo SDK 51에서 지원됩니다. Expo 프로젝트 내에서 React Native를 0.75.0으로 업데이트하는 방법은 이 전용 포스트에서 확인할 수 있습니다.

info

0.75 버전은 이제 React Native의 최신 안정 버전이며, 0.72.x 버전은 지원이 중단됩니다. 자세한 내용은 React Native의 지원 정책을 참고하세요. 곧 0.72 버전의 최종 종료 업데이트를 발표할 예정입니다.