기존 앱과의 통합
React Native는 새로운 모바일 앱을 처음부터 개발할 때 뛰어난 선택지다. 하지만 기존 네이티브 애플리케이션에 단일 뷰나 사용자 흐름을 추가하는 데도 효과적으로 사용할 수 있다. 몇 가지 단계를 거치면 React Native 기반의 새로운 기능, 화면, 뷰 등을 추가할 수 있다.
구체적인 단계는 타겟 플랫폼에 따라 다르다.
- Android (Java & Kotlin)
- iOS (Objective-C and Swift)
주요 개념
React Native 컴포넌트를 안드로이드 애플리케이션에 통합하는 핵심은 다음과 같다:
- 올바른 디렉터리 구조를 설정한다.
- 필요한 NPM 의존성을 설치한다.
- Gradle 설정에 React Native를 추가한다.
- 첫 번째 React Native 화면을 위한 TypeScript 코드를 작성한다.
- ReactActivity를 사용해 React Native와 안드로이드 코드를 통합한다.
- 번들러를 실행하고 애플리케이션이 동작하는 모습을 확인하며 통합을 테스트한다.
커뮤니티 템플릿 활용하기
이 가이드를 따라가면서 React Native 커뮤니티 템플릿을 참고 자료로 사용하는 것을 권장한다. 이 템플릿은 최소한의 안드로이드 앱을 포함하고 있어, 기존 안드로이드 앱에 React Native를 통합하는 방법을 이해하는 데 도움을 준다.
사전 준비
React Native 앱을 안드로이드용으로 빌드하기 위해 개발 환경을 설정하려면 개발 환경 설정 가이드와 프레임워크 없이 React Native 사용하기를 참고한다. 이 가이드는 또한 액티비티 생성이나 AndroidManifest.xml
파일 편집과 같은 안드로이드 개발의 기본 사항에 익숙하다고 가정한다.
1. 디렉터리 구조 설정
원활한 개발 환경을 위해 새로운 React Native 프로젝트 폴더를 생성한 후, 기존 Android 프로젝트를 /android
하위 폴더로 이동한다.
2. NPM 의존성 설치
프로젝트 루트 디렉터리로 이동한 후 다음 명령어를 실행한다:
curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/0.75-stable/template/package.json
이 명령어는 커뮤니티 템플릿의 package.json
파일을 프로젝트로 복사한다.
다음으로, NPM 패키지를 설치하기 위해 아래 명령어를 실행한다:
- npm
- Yarn
npm install
yarn install
설치 과정에서 새로운 node_modules
폴더가 생성된다. 이 폴더는 프로젝트를 빌드하는 데 필요한 모든 자바스크립트 의존성을 저장한다.
.gitignore
파일에 node_modules/
를 추가한다 (커뮤니티 기본 설정 참조).
3. 앱에 React Native 추가하기
Gradle 설정
React Native는 React Native Gradle Plugin을 사용해 의존성과 프로젝트 설정을 구성한다.
먼저, settings.gradle
파일을 수정해 다음 내용을 추가한다(커뮤니티 템플릿 참고):
// 자동 링크를 위해 React Native Gradle Settings 플러그인 설정
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
// .gradle.kts 파일 사용 시:
// extensions.configure<com.facebook.react.ReactSettingsExtension> { autolinkLibrariesFromCommand() }
includeBuild("../node_modules/@react-native/gradle-plugin")
// 기존 Gradle 모듈 포함
// include(":app")
그런 다음, 최상위 build.gradle
파일을 열어 다음 줄을 추가한다(커뮤니티 템플릿 참고):
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
+ classpath("com.facebook.react:react-native-gradle-plugin")
}
}
이렇게 하면 React Native Gradle Plugin(RNGP)이 프로젝트 내에서 사용 가능해진다.
마지막으로, 앱의 build.gradle
파일(보통 app
폴더 안에 위치)에 다음 내용을 추가한다(커뮤니티 템플릿 파일 참고):
apply plugin: "com.android.application"
+apply plugin: "com.facebook.react"
repositories {
mavenCentral()
}
dependencies {
// 기타 의존성
+ // RNGP가 버전 관리를 하므로 버전 번호를 명시하지 않는다.
+ // RNGP를 사용하지 않으면 수동으로 버전을 지정해야 한다.
+ implementation("com.facebook.react:react-android")
+ implementation("com.facebook.react:hermes-android")
}
+react {
+ // 자동 링크 활성화 - https://github.com/react-native-community/cli/blob/master/docs/autolinking.md
+ autolinkLibrariesWithApp()
+}
마지막으로, 앱의 gradle.properties
파일을 열어 다음 줄을 추가한다(커뮤니티 템플릿 파일 참고):
+reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
+newArchEnabled=true
+hermesEnabled=true
매니페스트 설정하기
먼저 AndroidManifest.xml
에 인터넷 권한이 있는지 확인한다:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication">
</application>
</manifest>
그런 다음 디버그용 AndroidManifest.xml
에서 클리어텍스트 트래픽을 활성화해야 한다:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
+ android:usesCleartextTraffic="true"
+ tools:targetApi="28"
/>
</manifest>
참고로, Community 템플릿의 AndroidManifest.xml 파일을 확인할 수 있다: main과 debug
이 설정은 애플리케이션이 로컬 번들러인 [Metro][https://metrobundler.dev/]와 HTTP를 통해 통신하기 위해 필요하다.
이 설정은 반드시 디버그용 매니페스트에만 추가해야 한다.
4. 타입스크립트 코드 작성
이제 실제로 네이티브 안드로이드 애플리케이션을 수정하여 React Native를 통합할 것이다.
첫 번째로 작성할 코드는 애플리케이션에 통합될 새로운 화면을 위한 React Native 코드이다.
index.js
파일 생성하기
먼저, React Native 프로젝트의 루트에 빈 index.js
파일을 만든다.
index.js
는 React Native 애플리케이션의 시작점이며 항상 필요하다. 이 파일은 React Native 컴포넌트나 애플리케이션의 일부인 다른 파일을 import
하는 간단한 파일일 수도 있고, 필요한 모든 코드를 포함할 수도 있다.
우리의 index.js
파일은 다음과 같이 작성한다 (여기서는 커뮤니티 템플릿 파일을 참고했다):
import {AppRegistry} from 'react-native';
import App from './App';
AppRegistry.registerComponent('HelloWorld', () => App);
App.tsx
파일을 만들어 보자. 이 파일은 TypeScript 파일로, JSX 표현식을 포함할 수 있다. 이 파일은 안드로이드 애플리케이션에 통합할 루트 React Native 컴포넌트를 담고 있다(참조 링크):
import React from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';
import {
Colors,
DebugInstructions,
Header,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
<View
style={{
backgroundColor: isDarkMode
? Colors.black
: Colors.white,
padding: 24,
}}>
<Text style={styles.title}>Step One</Text>
<Text>
Edit <Text style={styles.bold}>App.tsx</Text> to
change this screen and see your edits.
</Text>
<Text style={styles.title}>See your changes</Text>
<ReloadInstructions />
<Text style={styles.title}>Debug</Text>
<DebugInstructions />
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
title: {
fontSize: 24,
fontWeight: '600',
},
bold: {
fontWeight: '700',
},
});
export default App;
여기서 커뮤니티 템플릿 파일을 참조할 수 있다.
5. 안드로이드 코드와 통합하기
이제 React Native 런타임을 시작하고 React 컴포넌트를 렌더링하도록 지시하기 위해 몇 가지 네이티브 코드를 추가해야 한다.
Application 클래스 업데이트
먼저, React Native를 올바르게 초기화하기 위해 Application
클래스를 다음과 같이 업데이트한다.
- Java
- Kotlin
package <your-package-here>;
import android.app.Application;
+import com.facebook.react.PackageList;
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactHost;
+import com.facebook.react.ReactNativeHost;
+import com.facebook.react.ReactPackage;
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
+import com.facebook.react.defaults.DefaultReactHost;
+import com.facebook.react.defaults.DefaultReactNativeHost;
+import com.facebook.soloader.SoLoader;
+import com.facebook.react.soloader.OpenSourceMergedSoMapping
+import java.util.List;
-class MainApplication extends Application {
+class MainApplication extends Application implements ReactApplication {
+ @Override
+ public ReactNativeHost getReactNativeHost() {
+ return new DefaultReactNativeHost(this) {
+ @Override
+ protected List<ReactPackage> getPackages() { return new PackageList(this).getPackages(); }
+ @Override
+ protected String getJSMainModuleName() { return "index"; }
+ @Override
+ public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; }
+ @Override
+ protected boolean isNewArchEnabled() { return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; }
+ @Override
+ protected Boolean isHermesEnabled() { return BuildConfig.IS_HERMES_ENABLED; }
+ };
+ }
+ @Override
+ public ReactHost getReactHost() {
+ return DefaultReactHost.getDefaultReactHost(getApplicationContext(), getReactNativeHost());
+ }
@Override
public void onCreate() {
super.onCreate();
+ SoLoader.init(this, OpenSourceMergedSoMapping);
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
+ DefaultNewArchitectureEntryPoint.load();
+ }
}
}
// package <your-package-here>
import android.app.Application
+import com.facebook.react.PackageList
+import com.facebook.react.ReactApplication
+import com.facebook.react.ReactHost
+import com.facebook.react.ReactNativeHost
+import com.facebook.react.ReactPackage
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
+import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
+import com.facebook.react.defaults.DefaultReactNativeHost
+import com.facebook.soloader.SoLoader
+import com.facebook.react.soloader.OpenSourceMergedSoMapping
-class MainApplication : Application() {
+class MainApplication : Application(), ReactApplication {
+ override val reactNativeHost: ReactNativeHost =
+ object : DefaultReactNativeHost(this) {
+ override fun getPackages(): List<ReactPackage> = PackageList(this).packages
+ override fun getJSMainModuleName(): String = "index"
+ override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
+ override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
+ override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
+ }
+ override val reactHost: ReactHost
+ get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
+ SoLoader.init(this, OpenSourceMergedSoMapping)
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
+ load()
+ }
}
}
참고로, MainApplication.kt 커뮤니티 템플릿 파일을 활용할 수 있다.
ReactActivity
생성하기
마지막으로 ReactActivity
를 확장하는 새로운 Activity
를 생성해야 한다. 이 액티비티는 React Native 런타임을 시작하고 React 컴포넌트를 렌더링하는 역할을 담당한다.
- Java
- Kotlin
// package <your-package-here>;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
public class MyReactActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "HelloWorld";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new DefaultReactActivityDelegate(this, getMainComponentName(), DefaultNewArchitectureEntryPoint.getFabricEnabled());
}
}
// package <your-package-here>
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
class MyReactActivity : ReactActivity() {
override fun getMainComponentName(): String = "HelloWorld"
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
참고로, MainActivity.kt 커뮤니티 템플릿 파일을 확인할 수 있다.
새로운 액티비티를 생성할 때마다 AndroidManifest.xml
파일에 추가해야 한다. 또한 MyReactActivity
의 테마를 Theme.AppCompat.Light.NoActionBar
(또는 ActionBar가 없는 테마)로 설정해야 한다. 그렇지 않으면 React Native 화면 위에 ActionBar가 렌더링된다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication">
+ <activity
+ android:name=".MyReactActivity"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+ </activity>
</application>
</manifest>
이제 액티비티가 JavaScript 코드를 실행할 준비가 되었다.
6. 통합 테스트
React Native를 애플리케이션에 통합하기 위한 기본 단계를 모두 완료했다. 이제 Metro 번들러를 실행해 TypeScript 애플리케이션 코드를 번들로 빌드할 차례다. Metro의 HTTP 서버는 개발 환경에서 localhost
를 통해 번들을 시뮬레이터나 디바이스와 공유한다. 이를 통해 핫 리로딩이 가능해진다.
먼저 프로젝트 루트에 metro.config.js
파일을 다음과 같이 생성한다:
const {getDefaultConfig} = require('@react-native/metro-config');
module.exports = getDefaultConfig(__dirname);
참고를 위해 Community 템플릿 파일에서 metro.config.js
파일을 확인할 수 있다.
설정 파일을 준비한 후 번들러를 실행한다. 프로젝트 루트 디렉토리에서 다음 명령을 실행한다:
- npm
- Yarn
npm start
yarn start
이제 평소처럼 Android 앱을 빌드하고 실행한다.
앱 내부에서 React로 구동되는 Activity에 도달하면, 개발 서버에서 JavaScript 코드를 로드하고 다음과 같이 표시된다:

Android Studio에서 릴리스 빌드 생성하기
Android Studio를 사용해도 릴리스 빌드를 쉽게 만들 수 있다. 기존의 네이티브 Android 앱을 빌드하는 것과 동일한 과정으로 진행된다.
React Native Gradle Plugin이 APK나 App Bundle 내부에 JS 코드를 번들링하는 작업을 자동으로 처리해준다.
Android Studio를 사용하지 않는다면, 다음 명령어로 릴리스 빌드를 생성할 수 있다:
cd android
# 릴리스 APK 생성
./gradlew :app:assembleRelease
# 릴리스 AAB 생성
./gradlew :app:bundleRelease
이 시점에서 여러분은 평소처럼 앱 개발을 계속할 수 있다. React Native 작업에 대해 더 알고 싶다면 디버깅 및 배포 문서를 참고한다.
주요 개념
React Native 컴포넌트를 iOS 애플리케이션에 통합하기 위한 핵심은 다음과 같다:
- 올바른 디렉터리 구조를 설정한다.
- 필요한 NPM 의존성을 설치한다.
- Podfile 설정에 React Native를 추가한다.
- 첫 번째 React Native 화면을 위한 TypeScript 코드를 작성한다.
RCTRootView
를 사용해 React Native를 iOS 코드와 통합한다.- 번들러를 실행하고 앱이 작동하는지 확인하며 통합을 테스트한다.
이 가이드를 따라가면서 React Native 커뮤니티 템플릿을 참고 자료로 사용하는 것을 추천한다. 이 템플릿에는 최소한의 iOS 앱이 포함되어 있으며, 기존 iOS 앱에 React Native를 통합하는 방법을 이해하는 데 도움이 될 것이다.
사전 준비 사항
iOS용 React Native 앱 개발 환경 설정 가이드와 프레임워크 없이 React Native 사용하기 가이드를 따라 개발 환경을 구성한다. 이 가이드는 UIViewController
생성과 Podfile
파일 편집 같은 iOS 개발 기본 개념에 익숙하다고 가정한다.
1. 디렉터리 구조 설정
원활한 작업을 위해 새로운 React Native 통합 프로젝트 폴더를 생성한 후, 기존 iOS 프로젝트를 /ios
하위 폴더로 이동한다.
2. NPM 의존성 설치
프로젝트의 루트 디렉토리로 이동한 후 다음 커맨드를 실행한다:
curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/0.77-stable/template/package.json
이 커맨드는 커뮤니티 템플릿의 package.json
파일을 프로젝트로 복사한다.
다음으로, NPM 패키지를 설치한다:
- npm
- Yarn
npm install
yarn install
설치 과정에서 새로운 node_modules
폴더가 생성된다. 이 폴더는 프로젝트를 빌드하는 데 필요한 모든 자바스크립트 의존성을 저장한다.
.gitignore
파일에 node_modules/
를 추가한다. (커뮤니티 기본 .gitignore
참고)
3. 개발 도구 설치
Xcode 커맨드라인 도구
커맨드라인 도구를 설치한다. Xcode 메뉴에서 **Settings... (또는 Preferences...)**를 선택한다. Locations 패널로 이동한 후 Command Line Tools 드롭다운에서 최신 버전을 선택해 도구를 설치한다.
CocoaPods
CocoaPods는 iOS와 macOS 개발을 위한 패키지 관리 도구다. 이 도구를 사용하면 현재 프로젝트에 React Native 프레임워크 코드를 로컬로 추가할 수 있다.
CocoaPods는 Homebrew를 통해 설치하는 것을 권장한다:
brew install cocoapods
4. React Native 앱에 추가하기
CocoaPods 설정
CocoaPods를 설정하려면 두 가지 파일이 필요하다:
- Gemfile: 필요한 Ruby 의존성을 정의한다.
- Podfile: 의존성을 올바르게 설치하는 방법을 정의한다.
Gemfile을 설정하려면 프로젝트의 루트 디렉토리로 이동한 후 다음 명령어를 실행한다:
curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/0.77-stable/template/Gemfile
이 명령어는 템플릿에서 Gemfile을 다운로드한다.
마찬가지로 Podfile을 설정하려면 프로젝트의 ios
폴더로 이동한 후 다음 명령어를 실행한다:
curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/0.77-stable/template/ios/Podfile
Gemfile과 Podfile을 참고할 때는 커뮤니티 템플릿을 기준으로 사용한다.
이제 Ruby gems와 Pods를 설치하기 위해 몇 가지 추가 명령어를 실행해야 한다.
ios
폴더로 이동한 후 다음 명령어를 실행한다:
bundle install
bundle exec pod install
첫 번째 명령어는 Ruby 의존성을 설치하고, 두 번째 명령어는 React Native 코드를 앱에 통합해 iOS 파일이 React Native 헤더를 가져올 수 있도록 한다.
5. 타입스크립트 코드 작성
이제 실제로 네이티브 iOS 애플리케이션을 수정해 React Native를 통합해 보겠다.
가장 먼저 작성할 코드는 애플리케이션에 통합될 새로운 화면을 위한 React Native 코드다.
index.js
파일 생성하기
먼저, React Native 프로젝트의 루트 디렉토리에 빈 index.js
파일을 생성한다.
index.js
는 React Native 애플리케이션의 시작점이며, 항상 필요하다. 이 파일은 React Native 컴포넌트나 애플리케이션의 일부인 다른 파일을 import
하는 작은 파일이 될 수도 있고, 필요한 모든 코드를 포함할 수도 있다.
우리의 index.js
파일은 다음과 같이 작성한다. (여기서는 커뮤니티 템플릿 파일을 참고한다.)
import {AppRegistry} from 'react-native';
import App from './App';
AppRegistry.registerComponent('HelloWorld', () => App);
App.tsx
파일을 생성해 보자. 이 파일은 TypeScript로 작성되며, JSX 표현식을 포함할 수 있다. 이 파일은 iOS 애플리케이션에 통합할 루트 React Native 컴포넌트를 담고 있다(참고 링크):
import React from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';
import {
Colors,
DebugInstructions,
Header,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
<View
style={{
backgroundColor: isDarkMode
? Colors.black
: Colors.white,
padding: 24,
}}>
<Text style={styles.title}>Step One</Text>
<Text>
Edit <Text style={styles.bold}>App.tsx</Text> to
change this screen and see your edits.
</Text>
<Text style={styles.title}>See your changes</Text>
<ReloadInstructions />
<Text style={styles.title}>Debug</Text>
<DebugInstructions />
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
title: {
fontSize: 24,
fontWeight: '600',
},
bold: {
fontWeight: '700',
},
});
export default App;
여기서 커뮤니티 템플릿 파일을 참고할 수 있다.
5. iOS 코드와 통합하기
이제 React Native 런타임을 시작하고 React 컴포넌트를 렌더링하도록 지시하기 위해 네이티브 코드를 추가해야 한다.
요구사항
React Native는 AppDelegate
와 함께 동작해야 한다. 아래 내용은 여러분의 AppDelegate
가 다음과 같이 구성되어 있다고 가정한다.
- ObjectiveC
- Swift
#import "AppDelegate.h"
#import "ViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate {
UIWindow *window;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
window = [UIWindow new];
window.rootViewController = [ViewController new];
[window makeKeyAndVisible];
return YES;
}
@end
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 애플리케이션 런칭 후 커스터마이징을 위한 지점
window = UIWindow()
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
}
AppDelegate
클래스 업데이트
먼저, AppDelegate
클래스를 React Native가 제공하는 RCTAppDelegate
클래스를 상속받도록 확장해야 한다.
- ObjectiveC
- Swift
이를 위해 AppDelegate.h
파일과 AppDelegate.m
파일을 수정해야 한다:
AppDelegate.h
파일을 열고 다음과 같이 수정한다 (공식 템플릿의 AppDelegate.h 참조):
#import <UIKit/UIKit.h>
+#import <React-RCTAppDelegate/RCTAppDelegate.h>
-@interface AppDelegate : UIResponder <UIApplicationDelegate>
+@interface AppDelegate : RCTAppDelegate
@end
AppDelegate.mm
파일을 열고 다음과 같이 수정한다 (공식 템플릿의 AppDelegate.mm 참조):
#import "AppDelegate.h"
#import "ViewController.h"
+#import <React/RCTBundleURLProvider.h>
@interface AppDelegate ()
@end
@implementation AppDelegate {
UIWindow *window;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ self.automaticallyLoadReactNativeWindow = NO;
+ return [super application:application didFinishLaunchingWithOptions:launchOptions];
window = [UIWindow new];
window.rootViewController = [ViewController new];
[window makeKeyAndVisible];
return YES;
}
+- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
+{
+ return [self bundleURL];
+}
+- (NSURL *)bundleURL
+{
+#if DEBUG
+ return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
+#else
+ return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+#endif
+}
@end
위 코드를 살펴보자:
RCTAppDelegate
를 상속받고,RCTAppDelegate
의application:didFinishLaunchingWithOptions
메서드를 호출한다. 이는 React Native 초기화 과정을 기본 클래스에 위임한다.automaticallyLoadReactNativeWindow
를NO
로 설정하여RCTAppDelegate
를 커스터마이징한다. 이는 앱이UIWindow
를 직접 처리하고 React Native가 이를 신경쓰지 않아도 된다는 것을 지시한다.sourceURLForBridge:
와bundleURL
메서드는 앱이 React Native에게 렌더링할 JS 번들을 어디서 찾을 수 있는지 알려주는 데 사용된다.sourceURLForBridge:
는 이전 아키텍처에서 사용되며, 새로운 아키텍처에서 필요한bundleURL
메서드로 결정을 위임한다.
이를 위해 AppDelegate.swift
파일을 수정해야 한다:
AppDelegate.swift
파일을 열고 다음과 같이 수정한다 (공식 템플릿의 AppDelegate.swift 참조):
import UIKit
+import React_RCTAppDelegate
@main
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: RCTAppDelegate {
- var window: UIWindow?
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
+ self.automaticallyLoadReactNativeWindow = false
+ super.application(application, didFinishLaunchingWithOptions: launchOptions)
window = UIWindow()
- window?.rootViewController = ViewController()
- window?.makeKeyAndVisible()
+ window.rootViewController = ViewController()
+ window.makeKeyAndVisible()
return true
}
+ override func sourceURL(for bridge: RCTBridge) -> URL? {
+ self.bundleURL()
+ }
+ override func bundleURL() -> URL? {
+#if DEBUG
+ RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
+#else
+ Bundle.main.url(forResource: "main", withExtension: "jsbundle")
+#endif
+ }
}
위 코드를 살펴보자:
RCTAppDelegate
를 상속받고,RCTAppDelegate
의application(_:didFinishLaunchingWithOptions:)
메서드를 호출한다. 이는 React Native 초기화 과정을 기본 클래스에 위임한다.automaticallyLoadReactNativeWindow
를false
로 설정하여RCTAppDelegate
를 커스터마이징한다. 이는 앱이UIWindow
를 직접 처리하고 React Native가 이를 신경쓰지 않아도 된다는 것을 지시한다.sourceURLForBridge(for:)
와bundleURL()
메서드는 앱이 React Native에게 렌더링할 JS 번들을 어디서 찾을 수 있는지 알려주는 데 사용된다.sourceURLForBridge(for:)
는 이전 아키텍처에서 사용되며, 새로운 아키텍처에서 필요한bundleURL()
메서드로 결정을 위임한다.
React Native 뷰를 rootViewController에 표시하기
마지막으로, React Native 뷰를 표시할 수 있다. 이를 위해 JS 콘텐츠를 로드할 수 있는 뷰를 호스팅할 새로운 View Controller가 필요하다.
- Xcode에서 새로운
UIViewController
를 생성한다. (이를ReactViewController
라고 부르자.) - 초기
ViewController
가ReactViewController
를 표시하도록 한다. 앱에 따라 이를 수행하는 방법은 여러 가지가 있다. 이 예제에서는 React Native를 모달로 표시하는 버튼이 있다고 가정한다.
- ObjectiveC
- Swift
#import "ViewController.h"
+#import "ReactViewController.h"
@interface ViewController ()
@end
- @implementation ViewController
+@implementation ViewController {
+ ReactViewController *reactViewController;
+}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.systemBackgroundColor;
+ UIButton *button = [UIButton new];
+ [button setTitle:@"Open React Native" forState:UIControlStateNormal];
+ [button setTitleColor:UIColor.systemBlueColor forState:UIControlStateNormal];
+ [button setTitleColor:UIColor.blueColor forState:UIControlStateHighlighted];
+ [button addTarget:self action:@selector(presentReactNative) forControlEvents:UIControlEventTouchUpInside];
+ [self.view addSubview:button];
+ button.translatesAutoresizingMaskIntoConstraints = NO;
+ [NSLayoutConstraint activateConstraints:@[
+ [button.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [button.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [button.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
+ [button.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
+ ]];
}
+- (void)presentReactNative
+{
+ if (reactViewController == NULL) {
+ reactViewController = [ReactViewController new];
+ }
+ [self presentViewController:reactViewController animated:YES completion:nil];
+}
@end
import UIKit
class ViewController: UIViewController {
+ var reactViewController: ReactViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = .systemBackground
+ let button = UIButton()
+ button.setTitle("Open React Native", for: .normal)
+ button.setTitleColor(.systemBlue, for: .normal)
+ button.setTitleColor(.blue, for: .highlighted)
+ button.addAction(UIAction { [weak self] _ in
+ guard let self else { return }
+ if reactViewController == nil {
+ reactViewController = ReactViewController()
+ }
+ present(reactViewController!, animated: true)
+ }, for: .touchUpInside)
+ self.view.addSubview(button)
+
+ button.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ button.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
+ button.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
+ button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
+ button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
+ ])
}
}
ReactViewController
코드를 다음과 같이 업데이트한다.
- ObjectiveC
- Swift
#import "ReactViewController.h"
+#import <React-RCTAppDelegate/RCTRootViewFactory.h>
+#import <React-RCTAppDelegate/RCTAppDelegate.h>
@interface ReactViewController ()
@end
@implementation ReactViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
+ RCTRootViewFactory *factory = ((RCTAppDelegate *)RCTSharedApplication().delegate).rootViewFactory;
+ self.view = [factory viewWithModuleName:@"HelloWorld"];
}
@end
import UIKit
+import React_RCTAppDelegate
class ReactViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
+ let factory = (RCTSharedApplication()?.delegate as? RCTAppDelegate)?.rootViewFactory
+ self.view = factory?.view(withModuleName: "HelloWorld")
}
}
- 샌드박스 스크립팅을 비활성화해야 한다. 이를 위해 Xcode에서 앱을 클릭한 후 빌드 설정으로 이동한다. 'script'로 필터링하고
User Script Sandboxing
을NO
로 설정한다. 이 단계는 React Native와 함께 제공되는 Hermes 엔진의 디버그 버전과 릴리스 버전을 전환하기 위해 필요하다.
;
6. 통합 테스트
여러분은 React Native를 애플리케이션에 통합하기 위한 기본 단계를 모두 완료했다. 이제 Metro 번들러를 실행해 TypeScript 애플리케이션 코드를 번들로 빌드할 차례다. Metro의 HTTP 서버는 개발 환경에서 localhost
를 통해 시뮬레이터나 디바이스에 번들을 공유한다. 이를 통해 핫 리로딩이 가능해진다.
먼저 프로젝트 루트에 metro.config.js
파일을 다음과 같이 생성한다:
const {getDefaultConfig} = require('@react-native/metro-config');
module.exports = getDefaultConfig(__dirname);
참고로 metro.config.js 파일을 Community 템플릿 파일에서 확인할 수 있다.
설정 파일을 준비했다면 번들러를 실행할 차례다. 프로젝트 루트 디렉토리에서 다음 커맨드를 실행한다:
- npm
- Yarn
npm start
yarn start
이제 iOS 앱을 평소처럼 빌드하고 실행한다.
앱 내부에서 React로 구동되는 Activity에 도달하면, 개발 서버에서 JavaScript 코드를 로드하고 다음과 같은 화면을 표시한다:

Xcode에서 릴리스 빌드 생성하기
Xcode를 사용해 릴리스 빌드를 생성할 수도 있다. 추가로 필요한 단계는 앱이 빌드될 때 실행되는 스크립트를 추가해 JS와 이미지를 iOS 애플리케이션으로 패키징하는 것이다.
- Xcode에서 애플리케이션을 선택한다.
Build Phases
를 클릭한다.- 왼쪽 상단의
+
를 클릭하고New Run Script Phase
를 선택한다. Run Script
라인을 클릭하고 스크립트 이름을Bundle React Native code and images
로 변경한다.- 텍스트 박스에 다음 스크립트를 붙여넣는다.
set -e
WITH_ENVIRONMENT="$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
REACT_NATIVE_XCODE="$REACT_NATIVE_PATH/scripts/react-native-xcode.sh"
/bin/sh -c "$WITH_ENVIRONMENT $REACT_NATIVE_XCODE"
[CP] Embed Pods Frameworks
라는 스크립트 앞에 방금 추가한 스크립트를 드래그 앤 드롭한다.
이제 릴리스를 위해 앱을 빌드하면 정상적으로 동작할 것이다.
이제 여러분은 평소처럼 앱 개발을 계속 진행할 수 있다. React Native 작업에 대해 더 알고 싶다면 디버깅과 배포 문서를 참고한다.