React Native
고친 사람 github-actions[bot]
React Native 는 JavaScript 로 짠 코드 하나로 iPhone 앱과 Android 앱을 함께 만들게 해 주는 도구입니다. 화면은 웹 페이지가 아니라 각 운영체제가 원래 쓰는 화면 부품으로 그려집니다. 화면을 짜는 방식은 웹의 React 와 같습니다.
쉽고 빠른 이해
React Native 는 JavaScript 코드로 iPhone 과 Android 앱의 화면과 동작을 짜게 합니다. 로그인 화면 하나를 한 번 짜면 두 기기에서 모두 뜨는 식입니다.
이게 없으면 같은 앱을 iPhone 용은 Swift 로, Android 용은 Kotlin 으로 두 번 짜야 합니다. 짜는 사람도 둘, 고칠 곳도 둘이 됩니다.
어떻게 도는가:
- 개발자는 「이 값이면 화면이 이렇게 생긴다」를 JavaScript 로 적습니다
- 앱 안에 함께 실린 JavaScript 엔진이 그 코드를 돌려 화면 모습을 계산합니다
- React Native 가 그 모습을 운영체제의 진짜 화면 부품으로 옮겨 놓습니다
대가도 있습니다. JavaScript 쪽이 오래 걸리는 계산에 묶이면 터치에 답을 못 해 화면이 멈춘 듯 보입니다. 블루투스처럼 운영체제 기능을 깊이 쓰려면 결국 Swift 나 Kotlin 코드를 따로 짭니다.
두 기기용 앱을 한 팀이 함께 짤 때 씁니다. 화면을 매 순간 새로 그리는 게임에는 잘 쓰지 않습니다.
상세
React Native 는 Meta 가 만들어 오픈 소스로 내놓은 크로스플랫폼 프레임워크입니다. 크로스플랫폼 프레임워크는 코드 하나로 여러 운영체제용 앱을 만들게 해 주는 도구입니다. React Native 가 겨냥하는 운영체제는 iPhone 의 iOS 와 Android 둘입니다.
코드는 JavaScript 로 적습니다. 웹 브라우저에서 도는 바로 그 언어입니다. JavaScript 에 타입 표기를 더한 TypeScript 로 적는 팀도 많습니다.
모바일에서 네이티브 앱은 그 운영체제를 만든 회사가 내놓은 언어와 도구로 짠 앱을 말합니다. iPhone 앱은 Swift 로, Android 앱은 Kotlin 으로 짜는 것이 보통입니다. 두 운영체제를 다 지원하려면 같은 앱을 두 번 짜야 합니다. 기능 하나를 고쳐도 두 곳을 고칩니다.
React Native 는 이 두 벌 짜기를 줄이려고 나왔습니다. 화면과 앱 로직을 JavaScript 로 한 번 짜서 두 운영체제에 함께 올립니다. 운영체제마다 달라야 하는 곳만 따로 짭니다.
이 절은 두 벌 짜기를 피하던 앞선 방식인 웹뷰에서 출발합니다. React Native 가 그와 무엇이 다른지 본 뒤, 화면을 짜는 법과 코드가 도는 구조를 봅니다. 끝으로 이 구조가 치르는 값과 이 도구를 고르는 경우를 봅니다.
웹뷰로 감싼 앱
React Native 전에도 한 코드로 두 운영체제를 겨냥하는 방법이 있었습니다. 웹뷰를 쓰는 방법입니다. 웹뷰는 앱 안에 끼워 넣는 작은 브라우저 창입니다.
화면은 HTML(HyperText Markup Language)과 CSS(Cascading Style Sheets)로 짭니다. 웹 페이지를 짜는 그 언어들입니다. 이 페이지를 웹뷰에 띄우면 iPhone 에서도 Android 에서도 같은 화면이 뜹니다.
이 방식이 잃는 것은 생김새와 쓰는 느낌입니다. 웹뷰 속 버튼은 운영체제의 버튼이 아니라 웹 페이지 안에 그린 버튼입니다. 스크롤이 멈추는 느낌이나 글자 입력 칸의 동작이 그 기기의 다른 앱과 조금씩 어긋납니다.
운영체제의 화면 부품
React Native 는 웹뷰를 쓰지 않습니다. JavaScript 코드가 「여기 버튼, 그 아래 글자」라고 적으면 React Native 가 운영체제의 화면 부품을 만들어 놓습니다. iPhone 에서는 iOS 의 부품이 뜨고, Android 에서는 Android 의 부품이 뜹니다.
스크롤과 글자 입력도 그 기기의 다른 앱과 같게 움직입니다. 운영체제가 보기에는 React Native 앱의 화면도 네이티브 앱과 같은 부품으로 이루어져 있습니다.
React Native 가 기본으로 내놓는 부품이 각 운영체제에서 무엇이 되는지는 아래와 같습니다. 웹에서 비슷한 구실을 하는 HTML 태그도 함께 적었습니다.
| React Native | iOS | Android | 웹에서 비슷한 것 |
|---|---|---|---|
View |
UIView |
ViewGroup |
<div> |
Text |
UITextView |
TextView |
<p> |
Image |
UIImageView |
ImageView |
<img> |
ScrollView |
UIScrollView |
ScrollView |
스크롤되는 <div> |
TextInput |
UITextField |
EditText |
<input> |
View 는 다른 부품을 담는 상자입니다. 화면을 나누는 기본 단위라서 거의 모든 화면이 View 를 겹쳐 짭니다.
글자는 반드시 Text 안에 넣어야 합니다. 웹에서 <div> 안에 글자를 바로 쓰듯 View 안에 글자를 바로 쓰면 오류가 납니다.
글자를 그리는 부품이 운영체제마다 따로 있어서, React Native 가 글자를 그 부품에 맡겨야 하기 때문입니다.
React 로 짜는 화면
화면을 짜는 방식은 React 에서 왔습니다. React 는 웹 화면을 짜는 JavaScript 라이브러리입니다. 화면을 컴포넌트라는 조각으로 나눕니다. 조각마다 「이 값이면 이렇게 생긴다」를 함수로 적습니다.
값이 바뀌면 화면도 따라 바뀌어야 하는 값을 상태라고 부릅니다. 좋아요 수나 입력 칸에 친 글자가 상태입니다. 상태가 바뀌면 React 가 컴포넌트 함수를 다시 불러 새 화면 모습을 얻습니다. 그리고 바로 전 모습과 견줘 달라진 곳만 고칩니다.
React 코드에는 함수 안에 HTML 태그처럼 생긴 것이 섞여 있습니다. 이 문법이 JSX(JavaScript 문법 확장)입니다. 빌드할 때 평범한 JavaScript 함수 호출로 바뀝니다.
아래는 누를 때마다 숫자가 하나씩 오르는 추천 버튼입니다. 주석은 처음 화면에 뜨는 글자입니다.
import { useState } from 'react';
import { Pressable, Text } from 'react-native';
function LikeButton() {
const [likes, setLikes] = useState(0);
const label = `추천 ${likes}`; // 추천 0
return (
<Pressable onPress={() => setLikes(likes + 1)}>
<Text>{label}</Text>
</Pressable>
);
}
useState(0) 은 처음 값이 0 인 상태 하나를 만듭니다. 돌려받는 둘은 지금 값 likes 와, 값을 바꾸는 함수 setLikes 입니다.
버튼을 누르면 setLikes 가 값을 1 올립니다. React 가 LikeButton 을 다시 불러 글자가 「추천 1」로 바뀝니다.
Pressable 은 눌림을 받는 부품입니다. onPress 에는 눌렸을 때 돌 함수를 넘깁니다.
웹의 React 코드와 다른 것은 태그 이름뿐입니다. 웹이면 <button> 과 <span> 을 썼을 곳에 Pressable 과 Text 를 씁니다.
웹에서 React 를 쓰던 개발자는 새 문법을 거의 배우지 않고 앱 화면을 짭니다.
스타일과 Flexbox
React Native 에는 CSS 파일이 없습니다. 스타일은 JavaScript 객체로 적어 부품의 style 속성에 넘깁니다.
속성 이름은 CSS 와 비슷합니다. 대신 낱말을 붙여 씁니다. background-color 는 backgroundColor 처럼 둘째 낱말부터 대문자로 엽니다.
const styles = StyleSheet.create({
card: { padding: 16, backgroundColor: 'white' },
});
<View style={styles.card} />
StyleSheet.create 는 스타일 객체를 이름 붙여 모아 두는 함수입니다. 위 코드는 card 라는 스타일을 만들어 View 에 입혔습니다.
숫자에는 단위를 붙이지 않습니다. 기기 화면의 촘촘함과 상관없이 같은 크기로 보이는 논리 단위로 읽힙니다.
부품의 배치는 Flexbox 규칙으로 잡습니다. Flexbox 는 상자 안의 부품을 한 방향으로 늘어놓고 남는 공간을 나누는 CSS 의 배치 방식입니다. 웹과 다른 점은 기본 방향입니다. 웹의 Flexbox 는 부품을 가로로 늘어놓지만, React Native 는 세로로 쌓습니다.
이 배치 계산은 운영체제의 배치 기능에 맡기지 않습니다. React Native 에 딸린 배치 엔진이 두 운영체제에서 같은 계산을 합니다. 그래서 같은 스타일이 두 기기에서 같은 크기와 위치로 나옵니다. 이 엔진은 아래 「맞물림」 절에서 봅니다.
두 스레드
React Native 앱 안에서는 두 세계가 함께 돌아갑니다. 한쪽은 개발자가 쓴 JavaScript 코드가 도는 세계입니다. 다른 쪽은 운영체제의 화면 부품이 사는 세계입니다.
JavaScript 코드는 앱 안에 함께 실린 JavaScript 엔진이 돌립니다. JavaScript 엔진은 JavaScript 코드를 읽어 실행하는 프로그램입니다. 브라우저 안에도 하나씩 들어 있습니다. React Native 앱은 이 엔진을 앱 설치 파일에 넣어 갑니다.
두 세계는 서로 다른 스레드에서 실행됩니다. 스레드는 한 프로그램 안에서 따로 도는 실행 흐름입니다. 한 스레드가 긴 일에 묶여도 다른 스레드는 제 일을 계속할 수 있습니다.
JavaScript 코드는 JS(JavaScript) 스레드에서 실행됩니다. 화면 부품은 UI(User Interface, 사용자 인터페이스) 스레드에서 움직입니다. UI 스레드는 화면을 그리고 터치를 받는 스레드입니다. 운영체제는 이 스레드를 앱의 메인 스레드라고 부릅니다.
버튼을 한 번 누르면 일이 두 스레드를 오갑니다. 그 순서는 아래와 같습니다.
sequenceDiagram
participant 사용자
participant UI as UI 스레드
participant JS as JS 스레드
사용자->>UI: 버튼을 누른다
UI->>JS: 눌림을 넘긴다
Note over JS: onPress 가 상태를 바꾼다
Note over JS: React 가 새 모습을 계산한다
JS->>UI: 달라진 곳만 보낸다
Note over UI: 화면 부품을 고친다
UI->>사용자: 바뀐 글자가 보인다
브리지와 JSI
두 스레드가 말을 주고받는 방식은 한 번 크게 바뀌었습니다. 처음 구조에서는 둘 사이에 브리지가 있었습니다. 브리지는 한쪽이 보낼 말을 글자로 바꿔 모아 두었다가 다른 쪽으로 넘기는 통로입니다.
말은 JSON(JavaScript Object Notation) 글자로 바뀌어 건너갔습니다. 값을 이렇게 글자나 바이트로 바꾸는 일을 직렬화라고 합니다. 받는 쪽은 그 글자를 다시 값으로 풀어서 씁니다.
백엔드로 치면 두 서비스가 메시지 큐에 JSON 을 넣고 꺼내며 대화하는 모습과 닮았습니다.
브리지의 말은 비동기로 갔습니다. 보낸 쪽은 답을 기다리지 않고 제 일을 계속합니다. 그래서 JavaScript 쪽이 화면 부품의 크기를 물어 바로 답을 받는 일이 어려웠습니다. 화면이 자주 바뀌면 바꾸고 푸는 비용도 그만큼 쌓였습니다.
지금 구조에서는 두 세계 사이에 C++ 로 짠 층이 하나 끼어 있습니다. C++ 코드는 iOS 와 Android 에서 같은 코드로 돌아갑니다. 두 운영체제가 이 층 하나를 함께 씁니다.
브리지 자리에는 JSI(JavaScript Interface)가 들어섰습니다. JSI 는 JavaScript 코드가 이 C++ 층의 객체를 직접 쥐고 그 메서드를 부르게 해 주는 연결 부분입니다. 값을 JSON 글자로 바꾸지 않습니다. 필요하면 답을 기다리는 동기 호출도 됩니다.
화면 부품을 만들고 고치는 명령도 길이 바뀌었습니다. 브리지 시절에는 이 명령이 브리지를 건너 도착했습니다. 운영체제마다 따로 짠 코드가 그 명령을 받아 처리했습니다.
지금은 Fabric 이 C++ 층에서 이 일을 맡습니다. 명령은 JSI 를 거쳐 바로 닿습니다.
네이티브 모듈
JavaScript 만으로 닿지 않는 기능도 있습니다. 블루투스나 지문 인증처럼 운영체제가 Swift·Kotlin 쪽에만 열어 둔 기능이 그렇습니다.
그럴 때는 네이티브 모듈을 짭니다. 네이티브 모듈은 Swift 나 Kotlin 으로 짠 코드를 JavaScript 에서 부를 수 있는 함수로 내보내는 묶음입니다. 자주 쓰는 기능은 누군가 이미 모듈로 짜서 JavaScript 라이브러리 저장소 npm 에 올려 둔 경우가 많습니다.
네이티브 모듈을 부르는 길도 JSI 로 바뀌었습니다. 브리지 시절에는 앱이 뜰 때 네이티브 모듈을 모두 미리 불러 두었습니다. 호출은 브리지를 건넜습니다.
지금 방식인 TurboModules 는 모듈을 처음 쓸 때 불러옵니다. 부를 때는 JSI 로 직접 부릅니다. 앞의 Fabric 과 이 TurboModules 를 묶어 흔히 「새 아키텍처」라고 합니다.
네이티브 모듈에는 빌드가 따라붙습니다. 모듈을 하나 더하면 iOS 앱과 Android 앱을 다시 빌드해야 합니다. iOS 앱 빌드에는 Mac 과, Apple 의 개발 도구 모음인 Xcode 가 있어야 합니다.
JavaScript 번들
앱을 빌드하면 JavaScript 코드는 파일 하나로 묶여 앱 안에 실립니다. 이 파일을 번들이라고 부릅니다.
화면과 로직이 이 번들 안에 있으므로, 번들만 갈아 끼우면 JavaScript 쪽 수정을 사용자에게 보낼 수 있습니다. 앱 스토어에 새 버전을 올려 심사를 받지 않아도 됩니다. 앱을 다시 설치하지 않고 내려받아 바꾸는 이 방식을 OTA 업데이트(Over-the-Air Update, 무선 업데이트)라고 합니다.
네이티브 코드를 바꾼 수정은 이 방식으로 못 보냅니다. 번들이 담는 것은 JavaScript 뿐이기 때문입니다. 그리고 앱 스토어 규정은 이 방식으로 앱의 주된 용도를 바꾸는 것을 허용하지 않습니다.
React Native 가 치르는 값
첫째 대가는 JS 스레드가 막힐 때 생깁니다. JS 스레드가 오래 걸리는 계산에 묶이면 터치를 받아도 답을 못 합니다. 화면이 멈춘 듯 보입니다.
그래서 애니메이션은 JavaScript 를 거치지 않고 UI 스레드에서 돌도록 넘깁니다. Animated 에 useNativeDriver: true 를 주는 것이 그 방법입니다.
둘째는 두 기기의 모습이 조금씩 다르다는 것입니다. 같은 코드라도 두 운영체제의 부품이 달라서, 스위치나 날짜 고르기 부품의 생김새가 갈립니다. 두 기기에서 한 픽셀까지 같은 모습이 필요하면 이 방식으로는 맞추기 어렵습니다.
셋째는 네이티브 쪽을 아는 사람이 여전히 필요하다는 것입니다. React Native 버전을 올리면 네이티브 모듈이 따라오지 못해 빌드가 깨지는 일이 생깁니다. 그걸 고치려면 iOS 와 Android 의 빌드 도구를 다룰 줄 알아야 합니다.
넷째는 설치 파일 크기입니다. 설치 파일에는 JavaScript 엔진과 React Native 자신이 함께 실립니다. 같은 일을 하는 네이티브 앱보다 설치 파일이 커집니다.
다른 방식과 견주기
한 코드로 두 운영체제를 짜는 도구에는 Flutter 도 있습니다. Flutter 는 운영체제의 화면 부품을 쓰지 않고 자기 그리기 엔진으로 화면을 직접 그립니다. 언어도 JavaScript 가 아니라 Dart 입니다.
앞에서 본 방식들을 나란히 놓으면 이렇습니다.
| 방식 | 짜는 언어 | 화면을 그리는 것 |
|---|---|---|
| 네이티브 앱 | Swift · Kotlin, 운영체제마다 한 벌 | 운영체제의 화면 부품 |
| 웹뷰 앱 | HTML · CSS · JavaScript | 웹뷰 속 웹 페이지 |
| React Native | JavaScript | 운영체제의 화면 부품 |
| Flutter | Dart | Flutter 가 직접 그린 그림 |
React Native 와 네이티브 앱은 화면 부품이 같습니다. 짜는 언어만 다릅니다. React Native 와 Flutter 는 코드가 한 벌인 것이 같습니다. 화면을 그리는 주체가 다릅니다.
React Native 를 고르는 경우
웹에서 React 를 쓰던 팀이 두 운영체제용 앱을 함께 낼 때 자주 고릅니다. 웹과 앱이 같은 언어를 쓰니 화면 밖의 로직 일부를 나눠 쓸 수도 있습니다. 쇼핑이나 예약처럼 목록과 입력 칸이 중심인 앱이 여기에 잘 맞습니다.
화면을 매 순간 새로 그리는 게임은 React Native 로 잘 짜지 않습니다. 운영체제가 새로 내놓은 기능을 나오자마자 깊이 써야 하는 앱도 네이티브로 짜는 편입니다.
새 프로젝트는 흔히 Expo 로 시작합니다. Expo 는 React Native 위에 빌드·배포 도구와 자주 쓰는 네이티브 모듈을 얹어 둔 도구 모음입니다.
맞물림
React Native 는 혼자 앱을 이루지 않습니다. Meta 가 함께 내놓는 네 가지 도구와 맞물려 돌아갑니다. 화면 계산은 React 가, JavaScript 실행은 Hermes 가, 코드 묶기는 Metro 가, 배치 계산은 Yoga 가 맡습니다. 이 절은 넷이 React Native 와 어떻게 붙는지를 봅니다.
React 가 화면 모습을 계산한다
React 는 두 부분으로 나뉩니다. 하나는 컴포넌트를 부르고 달라진 곳을 찾는 공통 부분입니다. 다른 하나는 그 결과를 실제 화면에 옮기는 렌더러입니다.
웹에서는 react-dom 이 렌더러입니다. 앱에서는 React Native 가 렌더러입니다.
그래서 React Native 앱은 react 패키지를 따로 받아 씁니다. useState 같은 훅은 react 에서, View 같은 화면 부품은 react-native 에서 가져옵니다.
훅은 컴포넌트 함수 안에서 상태 같은 React 기능을 쓰게 해 주는 함수입니다. 앞의 추천 버튼 예제 첫 두 줄이 이렇게 둘을 따로 가져왔습니다.
React 와 붙어 있어서 버전을 맞춰야 합니다. React Native 의 한 버전은 정해진 React 버전과 함께 돌아갑니다. 웹에서 새 React 기능이 나와도, React Native 가 그 버전을 받아들이기 전에는 앱에서 못 씁니다.
Hermes 가 JavaScript 를 돌린다
Hermes 는 Meta 가 React Native 앱을 돌리려고 만든 JavaScript 엔진입니다. React Native 는 JS 스레드의 코드를 이 엔진에 맡깁니다.
브라우저의 엔진은 페이지를 열 때 JavaScript 글자를 읽어 해석합니다. Hermes 는 이 일을 앱을 빌드할 때 미리 해서 바이트코드로 바꿔 둡니다. 바이트코드는 엔진이 바로 실행할 수 있게 미리 번역해 둔 명령 묶음입니다. 앱이 뜰 때 해석할 일이 없어 첫 화면이 빨리 뜹니다.
대가는 오래 도는 계산입니다. Hermes 는 자주 도는 코드를 실행 중에 기계어로 바꾸는 JIT 컴파일(Just-In-Time Compilation, 실행 중 컴파일)을 하지 않습니다. 계산을 오래 돌리면 JIT 컴파일을 하는 엔진보다 느릴 수 있습니다.
Metro 가 코드를 묶고 나른다
Metro 는 React Native 용 번들러입니다. 번들러는 여러 파일로 나뉜 JavaScript 코드를 파일 하나로 묶는 도구입니다. 코드가 가져다 쓰는 라이브러리도 따라가서 함께 묶습니다. 앞에서 본 번들을 만드는 것이 Metro 입니다.
개발 중에는 Metro 가 개발자 컴퓨터에서 서버로 떠 있습니다. 앱은 번들을 이 서버에서 받아 옵니다. 코드를 저장하면 Metro 가 바뀐 모듈만 다시 보냅니다. 앱은 다시 빌드하지 않고 화면에 바로 반영합니다. 이 기능을 Fast Refresh 라고 부릅니다.
Metro 가 닿지 못하는 곳도 있습니다. Metro 가 다시 보내는 것은 JavaScript 뿐입니다. Swift·Kotlin 코드나 네이티브 모듈을 바꾸면 앱을 다시 빌드해 기기에 다시 설치해야 합니다. 웹에서 같은 일을 하는 번들러로는 webpack 과 Vite 가 있습니다.
Yoga 가 배치를 계산한다
Yoga 는 Flexbox 규칙으로 부품의 크기와 위치를 계산하는 배치 엔진입니다. C++ 로 짜여 있어 iOS 와 Android 에서 같은 코드가 돌아갑니다. 앞의 「스타일과 Flexbox」에서 말한 배치 엔진이 이것입니다.
React Native 는 스타일 객체에 적힌 flexDirection 이나 padding 같은 값을 Yoga 에 넘깁니다. Yoga 는 부품마다 위치와 너비·높이를 계산해 돌려줍니다.
React Native 는 그 숫자대로 운영체제의 화면 부품을 놓습니다.
Yoga 에 배치를 맡기는 만큼 웹 CSS 의 배치 방식 가운데 일부만 됩니다. Yoga 가 아는 것은 Flexbox 와 위치 지정 정도입니다.
웹 CSS 의 float 처럼 Flexbox 밖의 배치 방식은 대부분 쓸 수 없습니다.
관련 항목
React Native 가 속하는 상위 분류
크로스플랫폼 프레임워크 · 프레임워크 · 모바일 개발 · UI 툴킷 · 오픈 소스
React Native 앱이 도는 운영체제
iOS · Android · iPadOS · macOS · Windows
React Native 코드를 짜는 언어와 문법
JavaScript · TypeScript · JSX · C++
React Native 와 맞물리는 Meta 의 도구
React · Hermes · Metro · Yoga
React Native 화면을 이루는 구성 요소
컴포넌트 · UI 상태 · 훅 · props · Flexbox · 가상 DOM · 렌더러
React Native 내부 구조를 이루는 부품
자바스크립트 엔진 · 스레드 · 메인 스레드 · 브리지 · JSI · Fabric · TurboModules · 네이티브 모듈 · Codegen
React Native 앱을 빌드하고 배포하는 도구
Expo · 번들러 · Fast Refresh · OTA 업데이트 · Xcode · Android Studio · CocoaPods · Gradle · npm · 앱 스토어
React Native 의 동작을 떠받치는 기반 개념
직렬화 · JSON · 비동기 · 바이트코드 · JIT 컴파일 · 선언형 프로그래밍
React Native 의 네이티브 모듈을 짜는 언어
Swift · Kotlin · Objective-C · Java
React Native 와 같은 일을 두고 겨루는 앱 제작 방식
Flutter · 웹뷰 · 네이티브 앱 · SwiftUI · Jetpack Compose · Kotlin Multiplatform · Ionic · Apache Cordova · 프로그레시브 웹 앱
다른 이름: 리액트 네이티브 · react-native · RN