기존 애플리케이션과의 통합
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 사용하기 문서를 통해 React Native 앱을 빌드할 기본 설정을 마친다. 이 가이드는 Android 개발의 기본 개념, 예를 들어 Activity 생성이나 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
폴더가 생성된다. 이 폴더는 프로젝트를 빌드하는 데 필요한 모든 자바스크립트 의존성을 저장한다.
node_modules/
를 .gitignore
파일에 추가한다. (커뮤니티 기본 설정 참고)
3. 앱에 React Native 추가하기
Gradle 설정
React Native는 React Native Gradle 플러그인을 사용해 의존성과 프로젝트 설정을 구성한다.
먼저, 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 플러그인(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 컴포넌트를 담고 있으며, 이를 Android 애플리케이션에 통합할 것이다 (참고 링크):
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 클래스 업데이트
먼저, Application
클래스를 업데이트하여 React Native를 올바르게 초기화한다. 아래와 같이 수정한다:
- 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
를 상속받아 React Native 코드를 호스팅할 새로운 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);
참고용으로 metro.config.js 파일을 커뮤니티 템플릿에서 확인할 수 있다.
설정 파일이 준비되면 번들러를 실행한다. 프로젝트 루트 디렉토리에서 다음 커맨드를 실행한다:
- npm
- Yarn
npm start
yarn start
이제 일반적인 방식으로 Android 앱을 빌드하고 실행한다.
앱 내부에서 React로 구동되는 Activity에 도달하면 개발 서버에서 JavaScript 코드를 로드하고 화면에 표시한다:

Android Studio에서 릴리스 빌드 생성하기
Android Studio를 사용해 릴리스 빌드를 생성할 수도 있다. 기존 네이티브 Android 앱의 릴리스 빌드를 생성하는 것만큼 간단하다.
React Native Gradle Plugin이 JS 코드를 APK/App Bundle 내부로 번들링하는 작업을 처리한다.
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를 통합하는 방법을 이해하는 데 도움이 될 것이다.
사전 준비 사항
React Native 앱을 iOS용으로 빌드하기 위해 개발 환경을 설정하려면 개발 환경 설정 가이드와 프레임워크 없이 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/
를 추가한다. (커뮤니티 기본 설정 참조).
3. 개발 도구 설치
Xcode 커맨드라인 도구
커맨드라인 도구를 설치한다. Xcode 메뉴에서 **Settings... (또는 Preferences...)**를 선택한다. Locations 패널로 이동한 후, Command Line Tools 드롭다운에서 최신 버전을 선택해 도구를 설치한다.
CocoaPods
CocoaPods는 iOS와 macOS 개발을 위한 패키지 관리 도구이다. 이 도구를 사용하면 현재 프로젝트에 React Native 프레임워크 코드를 로컬로 추가할 수 있다.
Homebrew를 통해 CocoaPods를 설치하는 것을 권장한다:
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 {
// Override point for customization after application launch.
window = UIWindow()
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
}
AppDelegate
클래스 업데이트
먼저, AppDelegate
클래스가 React Native에서 제공하는 RCTAppDelegate
클래스를 상속받도록 수정해야 한다.
- ObjectiveC
- Swift
이를 위해 AppDelegate.h
파일과 AppDelegate.mm
파일을 다음과 같이 수정한다:
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 엔진의 Debug와 Release 버전 간 전환을 올바르게 수행하기 위해 필요하다.
;
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 파일을 커뮤니티 템플릿 파일에서 확인할 수 있다.
설정 파일을 준비한 후, 번들러를 실행한다. 프로젝트 루트 디렉토리에서 다음 커맨드를 실행한다:
- 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 작업에 대해 더 알고 싶다면 디버깅과 배포 문서를 참고한다.