React Native1편

React Native란 무엇인가 — 웹이 아닌 화면을 React로 그리기

React Native는 웹뷰로 감싼 하이브리드 앱이 아닙니다. React의 사고방식은 그대로 두고 '무엇을 그릴 것인가'만 바꾼다는 관점에서, React Native의 정체와 웹과의 경계를 짚습니다.

앞선 「React 기초」 시리즈에서 우리는 화면을 상태의 함수로 선언하고, 그 화면을 컴포넌트로 조립하는 방식을 익혔습니다. 그 감각이 손에 붙고 나면 자연스럽게 드는 질문이 하나 있습니다. “그럼 앱은요?”

React Native는 그 질문에 대한 대답입니다. 그런데 이 이름은 오해를 참 많이 삽니다. “웹을 앱처럼 포장한 것 아니냐”, “결국 웹뷰 아니냐” 같은 이야기가 아직도 돌아다니고, 심지어 오래된 설명글들이 그렇게 적고 있습니다. 그러니 첫 편은 무엇을 만드는 법이 아니라, React Native가 정확히 무엇인지부터 정리하고 시작하겠습니다.


React는 원래 ‘화면을 그리는 법’을 모른다

이상하게 들리겠지만 사실입니다. React 자체는 DOM을 모릅니다.

1부에서 컴포넌트가 반환하는 JSX는 실제 화면이 아니라 “화면이 이렇게 생겨야 한다”는 설계도라고 했습니다. 그 설계도를 받아 실제 화면으로 옮기는 일은 React가 아니라 렌더러(Renderer)라는 별도의 담당자가 합니다. 우리가 웹에서 늘 함께 설치하던 react-dom이 바로 그 담당자였습니다.

// 웹: react-dom이 이 설계도를 실제 DOM 노드로 옮긴다
import { createRoot } from 'react-dom/client';

여기서 발상 하나가 나옵니다. 설계도를 만드는 쪽(React)과 그것을 그리는 쪽(렌더러)이 분리되어 있다면, 그리는 쪽만 바꿔 끼우면 되지 않을까? DOM 대신 iOS와 Android의 네이티브 UI를 그리는 렌더러를 만들어 붙이면, React로 선언한 화면이 모바일 앱의 화면이 됩니다.

그게 React Native입니다. React Native는 React를 흉내 낸 다른 무언가가 아니라, 똑같은 React에 렌더러만 갈아 끼운 것입니다. 이 문장이 이 시리즈 전체의 전제입니다. 여러분이 이미 아는 컴포넌트·Props·State·useEffect·Context는 전부 그대로 쓰입니다.


웹뷰 하이브리드가 아니다

가장 흔한 오해를 확실히 끊고 가겠습니다. React Native는 화면을 웹뷰(WebView)에 띄우지 않습니다.

이름이 비슷한 다른 접근이 실제로 존재하기 때문에 생긴 혼동입니다. Cordova나 Ionic 계열은 앱 껍데기 안에 브라우저 화면을 하나 띄우고 그 안에서 HTML을 렌더합니다. 이런 방식을 하이브리드 앱이라 부르고, 그래서 “결국 안은 웹”이라는 지적이 따라붙습니다.

React Native는 다릅니다. 우리가 <View>라고 적으면 그것은 iOS에서는 UIView, Android에서는 android.view.ViewGroup이라는 진짜 네이티브 뷰 객체가 됩니다. <Text>는 각 플랫폼의 실제 텍스트 뷰가 되고, 스크롤은 그 플랫폼이 원래 쓰던 스크롤 뷰가 처리합니다. 화면에 보이는 버튼은 브라우저가 흉내 낸 버튼이 아니라 OS가 그린 버튼입니다.

그래서 React Native 앱에는 divspan도 없고, HTML도 CSS 파일도 없습니다. 웹 기술을 앱 안에 넣은 것이 아니라, React라는 사고방식만 가져가고 재료는 전부 네이티브로 바꾼 것입니다.


그럼 자바스크립트와 네이티브는 어떻게 대화하는가

우리가 쓰는 컴포넌트 코드는 자바스크립트입니다. 실제 화면을 그리는 것은 네이티브입니다. 이 둘 사이에 통로가 필요합니다.

예전 React Native는 이 통로를 Bridge라고 불렀습니다. 자바스크립트 쪽과 네이티브 쪽이 서로를 직접 부르지 못하고, 메시지를 JSON 문자열로 만들어 큐에 실어 비동기로 주고받는 구조였습니다. 우체통에 편지를 넣고 답장을 기다리는 셈이라, 메시지가 몰리면 화면이 한 박자 늦게 반응하는 문제가 있었습니다. 오래된 글에서 “React Native는 브릿지로 통신한다”는 설명을 보게 되는 건 이 시절의 유산입니다.

지금은 다릅니다. New Architecture라 불리는 구조로 바뀌었고, 0.76 버전부터 이것이 기본값입니다. 세 가지 이름만 알아두면 충분합니다.

  • JSI(JavaScript Interface) — 자바스크립트가 네이티브 객체를 직접 호출할 수 있게 하는 층입니다. 편지를 부치는 대신 전화를 거는 쪽에 가까워졌습니다. 우체통(Bridge)이 사라진 자리입니다.
  • Fabric — 새 렌더링 시스템입니다. 레이아웃 계산과 화면 반영이 이전보다 긴밀하게 맞물려, 스크롤 중 깜빡임 같은 타이밍 문제가 줄었습니다.
  • TurboModules — 카메라·위치 같은 네이티브 기능 모듈을 앱 시작 때 전부 올리지 않고 필요할 때 불러오는 방식입니다. 앱이 뜨는 시간이 짧아집니다.

지금 이 셋의 내부를 깊이 알 필요는 없습니다. 다만 “자바스크립트가 네이티브를 느리게 우회해서 부른다”는 낡은 인상만은 버려두시면 됩니다. 실무에서 마주치는 성능 문제는 대개 이 통로가 아니라 우리가 리스트를 잘못 그리는 데서 옵니다. 그 이야기는 5편에서 하겠습니다.


무엇이 같고 무엇이 다른가

React를 아는 사람에게 가장 유용한 지도는 “어디까지가 이미 아는 땅인가”입니다.

그대로인 것 — 컴포넌트로 화면을 조립하는 방식, JSX 문법, Props로 값을 내려보내는 것, useState로 상태를 들고 리렌더가 일어나는 것, useEffect·useRef·Context, 그리고 npm 생태계. 1부에서 배운 것은 버릴 게 없습니다.

달라지는 것은 정확히 ‘화면의 재료’ 쪽입니다.

React Native
div, span, pView, Text
CSS 파일, 클래스StyleSheet(자바스크립트 객체)
URL과 라우터네비게이션(화면 스택)
onClickonPress
브라우저 API네이티브 API + 권한 요청

여기서 초심자가 가장 자주 걸려 넘어지는 두 가지만 미리 말해두겠습니다. 첫째, 모든 글자는 <Text> 안에 있어야 합니다. 웹에서처럼 <View>여기 글씨</View>라고 쓰면 오류가 납니다. 둘째, 스타일은 상속되지 않고, Flexbox의 기본축이 가로가 아니라 세로입니다. 웹의 습관 그대로 쓰면 십중팔구 화면이 예상과 다르게 나옵니다. 3편과 4편에서 각각 자세히 다룹니다.


솔직한 트레이드오프

“하나의 코드로 두 플랫폼”은 사실이지만, “네이티브를 몰라도 된다”는 뜻은 아닙니다.

앱을 만들다 보면 결국 iOS와 Android의 차이를 마주합니다. 권한을 요청하는 흐름이 다르고, 뒤로 가기 버튼의 존재가 다르고, 화면 상단의 노치와 하단의 홈 인디케이터를 피하는 방법도 다릅니다. React Native는 이 차이를 지워주지 않고, 다룰 수 있는 손잡이를 줄 뿐입니다. 가끔은 원하는 기능을 지원하는 라이브러리가 없어 네이티브 모듈을 직접 붙여야 하는 순간도 옵니다.

그럼에도 이 선택이 합리적인 경우가 많습니다. 화면과 비즈니스 로직의 대부분을 한 벌만 쓰고, 팀이 이미 아는 React 지식을 그대로 재사용하며, 코드를 고치자마자 앱에 반영되는 개발 경험을 얻기 때문입니다. 다만 무거운 그래픽 처리나 플랫폼 최신 기능을 가장 먼저 써야 하는 앱이라면, 여전히 네이티브가 정답일 수 있습니다. 도구는 만능이 아니라 적합한 자리가 있을 뿐입니다.


앞으로 다룰 것

정리하면 React Native는 세 문장입니다. React의 사고방식은 그대로 쓴다. 화면은 웹뷰가 아니라 진짜 네이티브 뷰로 그려진다. 대신 DOM과 CSS라는 재료는 버리고 새 재료를 배운다.

다음 편에서는 Expo로 개발 환경을 만들고 실제 기기에 첫 화면을 띄워보겠습니다. 이후로는 네이티브 컴포넌트, StyleSheet와 Flexbox, 리스트와 터치, 네비게이션, 플랫폼 API, 그리고 스토어 배포까지 차례로 이어집니다. 웹에서 React를 익힌 손으로, 이번에는 주머니 속에서 돌아가는 화면을 그려볼 차례입니다.