Skip to main content
네이티브
입문네이티브React Native 플랫폼 · 1

React Native의 렌더 대상: 컴포넌트는 어떻게 실제 앱 UI가 되는가

사용자 컴포넌트, React Native 호스트 컴포넌트와 Android·iOS 호스트 뷰를 구분하고, 보이는 구조와 실제 네이티브 UI 계층이 다를 때의 디버깅 기준을 세운다.

마지막 검증 재검증 정책: 제품 버전 의존: 새 주요 버전마다 재검증
목차
표준·구현·측정·해석 표시는 무엇인가요?
  • 표준웹 표준이나 언어 명세가 정한 동작
  • 구현특정 기술이나 브라우저가 실제로 구현한 동작
  • 측정명시한 환경에서 직접 실행해 관찰한 결과
  • 해석앞선 근거에서 도출한 설계 판단
  • 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용

30초 요약

React Native에서 화면을 볼 때는 세 대상을 구분한다.

  1. PaymentTarget 같은 사용자 컴포넌트는 props를 받아 다른 컴포넌트를 조합한다.
  2. View, Text 같은 React 호스트 컴포넌트는 렌더러가 이해하는 기본 UI 입력이다.
  3. 호스트 뷰는 Android와 iOS가 실제로 표시하고 입력을 처리하는 UI 요소다.

이 세 구조는 일대일이 아니다. 사용자 컴포넌트 이름 자체는 플랫폼 UI 요소로 직접 남지 않고, 배치만 위한 View도 최적화 과정에서 실제 네이티브 계층에 따로 남지 않을 수 있다. 따라서 “화면에 보인다”와 “외부 네이티브 기능이 특정 뷰를 찾는다”는 서로 다른 검증이다.

이 글을 관통하는 상황: 버튼은 보이는데 안내 기능이 대상을 못 찾는다

결제 버튼 위에 안내 말풍선을 보여 주는 네이티브 SDK(Software Development Kit)가 있다고 하자. SDK는 플랫폼 기능을 앱에 제공하는 라이브러리와 도구 묶음이다. 이 SDK는 pay-button이라는 식별자가 붙은 실제 플랫폼 UI 요소를 찾는다. 다음 PaymentTargetnativeID를 props로 받지만 내부 View에 전달하지 않는다.

React Native 0.86 기본 개발 환경이 준비된 작은 앱의 App.tsx를 다음 코드로 바꾼다. 별도 SDK가 없어도 버튼이 보이고 눌리는지까지는 실행할 수 있도록 결제 동작을 화면 문구 변경으로 대체했다. Pressable은 누르기 상호작용을 처리하는 React Native 컴포넌트다.

import { useState, type ReactNode } from 'react'
import { Pressable, Text, View } from 'react-native'
 
type PaymentTargetProps = {
  nativeID: string
  children: ReactNode
}
 
function PaymentTarget({ nativeID, children }: PaymentTargetProps) {
  return <View style={{ padding: 12 }}>{children}</View>
}
 
export default function Checkout() {
  const [message, setMessage] = useState('대기 중')
 
  function startPayment() {
    setMessage('결제 시작')
  }
 
  return (
    <View style={{ padding: 24 }}>
      <PaymentTarget nativeID="pay-button">
        <Pressable onPress={startPayment}>
          <Text>결제하기</Text>
        </Pressable>
      </PaymentTarget>
      <Text>상태: {message}</Text>
    </View>
  )
}

화면 모양에는 문제가 없다. 그러나 nativeID 변수는 읽히지 않은 채 버려진다. JSX에 <PaymentTarget nativeID="pay-button">을 썼다는 사실만으로 렌더러가 어느 내부 요소에 식별자를 붙여야 하는지 알 수는 없다.

여기서 먼저 질문할 것은 “왜 SDK가 React 컴포넌트를 못 찾지?”가 아니다.

식별자가 실제 어떤 호스트 컴포넌트까지 전달됐는가?

사용자 컴포넌트와 호스트 컴포넌트는 역할이 다르다

PaymentTargetView 두 개를 반환하도록 바꾸면 네이티브 후보 구조도 달라질 수 있다. 반대로 여러 사용자 컴포넌트가 최종적으로 같은 View 하나를 반환할 수도 있다. 컴포넌트 파일 수나 JSX 이름으로 실제 네이티브 뷰 수를 세면 안 된다.

Web React의 <div>가 브라우저 DOM을 대상으로 하는 것처럼 React Native의 View는 플랫폼 UI를 대상으로 한다. 하지만 브라우저 DOM 검사법을 그대로 모바일에 적용할 수는 없다.

Render, Commit, Mount 사이에는 내부 UI 트리가 있다

여기서는 React 계산 결과와 실제 플랫폼 UI 사이에 별도의 내부 계산·반영 단계가 있다는 경계만 사용한다. 세부 구현 책임은 뒤의 글에서 하나씩 다룬다.

이 글에서 중요한 결론은 내부 구현 이름을 외우는 것이 아니다.

사용자 컴포넌트 계산

React Native 호스트 컴포넌트

React Shadow Tree와 레이아웃 계산

Android·iOS 호스트 뷰 변경

React 컴포넌트 함수가 실행됐다는 로그는 첫 단계의 증거다. 실제 Android·iOS UI 요소가 생겼다는 증거는 마지막 단계에서 확인해야 한다.

보이는 UI와 네이티브 대상 계약은 다르다

이 사례의 최소 교정은 식별자를 실제 대상 View까지 전달하는 것이다.

function PaymentTarget({ nativeID, children }: PaymentTargetProps) {
  return (
    <View nativeID={nativeID} style={{ padding: 12 }}>
      {children}
    </View>
  )
}

이제 다음을 서로 다른 증거로 확인한다.

  • 화면: 결제 버튼과 배치가 이전과 같은가?
  • React 경계: PaymentTarget이 받은 nativeIDView에 전달하는가?
  • 네이티브 경계: Android와 iOS에서 SDK가 pay-button 대상을 실제로 찾는가?

이 구분은 접근성, 자동화 테스트, 네이티브 애니메이션과 측정 API에도 적용된다. 사용자 컴포넌트의 편리한 공개 props와 실제 호스트 컴포넌트에 필요한 props 사이의 번역 책임을 컴포넌트 계약으로 명시한다.

실제 네이티브 계층은 필요한 경계에서만 고정한다

다음처럼 스타일도 식별자도 없는 중간 View는 JSX 구조를 정리하는 데는 편하지만, 화면에 독립적으로 그릴 내용이 없다.

<View>
  <View>
    <Text>결제하기</Text>
  </View>
</View>

따라서 앞서 교정한 nativeID={nativeID} 대상에 다시 collapsable={false}를 붙일 필요는 없다. 반면 특정 네이티브 자식 뷰를 직접 열거하는 SDK처럼 정확한 중간 계층 자체가 계약이면 그 계층에만 다음처럼 지정할 수 있다.

<View collapsable={false}>
  <NativeSdkTarget />
</View>

이 옵션을 모든 View에 일괄 적용하지 않는다. 화면 결과가 같다면 플래트닝은 불필요한 실제 뷰를 줄이는 최적화다. 먼저 SDK 문서에서 필요한 계층 계약을 확인하고, 필요한 한 경계만 보존한 뒤 양쪽 플랫폼에서 검사한다.

검증도 React 경계와 플랫폼 경계로 나눈다

검증 순서는 다음처럼 좁혀 간다.

  1. bad 코드를 실행해 버튼과 상태: 결제 시작은 정상임을 확인한다. 코드에서는 실제 ViewnativeID가 없어 식별자 전달 계약이 실패했음을 확인한다.
  2. 교정 코드로 바꾼 뒤 같은 화면과 버튼 동작이 유지되고 실제 ViewnativeID="pay-button"이 전달되는 코드를 확인한다.
  3. 컴퓨터에서 Android 기기를 가상 실행하는 emulator에서는 Android Studio의 Layout Inspector로, Mac에서 iOS 실행 환경을 모사하는 simulator에서는 Xcode의 Debug View Hierarchy로 결제 버튼 영역을 감싸는 대상 플랫폼 뷰와 필요한 부모·자식 계층을 확인한다. 이 사례에서는 bad와 good의 보이는 계층이 같아도 정상이다. 이 도구는 실제 계층의 증거이지 nativeID 전달이나 특정 SDK의 식별자 탐색 성공을 대신하는 증거는 아니다.
  4. 실제 안내 SDK를 연결한 양쪽 플랫폼 통합 환경에서 bad 코드는 대상을 찾지 못하고, good 코드는 pay-button을 찾아 같은 위치에 말풍선을 배치하는지 확인한다.
  5. SDK가 요구하는 기능을 가상 기기가 제공하지 않거나, 제공하더라도 실제 하드웨어 동작과 성능을 확인하기에 충실도가 부족할 때 지원 범위의 실기기에서도 확인한다.

위 로컬 예제만 실행하면 보이는 UI가 동일하다는 사실과 props 전달 코드의 차이까지 증명한다. 가상의 안내 SDK가 실제로 식별자를 찾았다고 증명하지 않는다. SDK가 없는 환경에서는 4단계를 미검증으로 기록하고, 실제 제품 통합 테스트에서 그 경계를 닫는다.

simulator·emulator의 성공을 곧바로 모든 실기기 성공으로 확대하지 않는다. 반대로 단순한 props 전달 오류를 찾기 위해 처음부터 모든 기기 조합을 실행하지도 않는다. 실패가 놓인 경계에 맞는 가장 작은 증거부터 시작한다.

같은 상황을 다시 진단한다

문제       결제 버튼은 보이지만 네이티브 안내 SDK가 대상을 찾지 못함
잘못된 판단 PaymentTarget이 화면에 있으므로 같은 이름의 네이티브 요소도 있을 것임
관찰       nativeID가 사용자 컴포넌트에서 끝나고 실제 View로 전달되지 않음
원인       사용자 컴포넌트와 호스트 뷰를 같은 구조로 봄
행동       식별자를 계약으로 정한 실제 View에 전달하고 필요한 계층만 보존
검증       props 전달 코드를 본 뒤 Android·iOS 실제 계층과 SDK 대상 탐색을 각각 확인

풀 리퀘스트에서는 다음을 확인한다.

  • 사용자 컴포넌트의 props가 실제 어느 호스트 컴포넌트로 전달되는지 분명한가?
  • 화면 모양만 보고 접근성·식별자·측정·외부 SDK 계약까지 성공했다고 판단하지 않았는가?
  • collapsable={false}가 필요한 정확한 네이티브 계층 계약이 있는가?
  • React 코드 확인과 Android·iOS 통합 검증이 각각 무엇을 증명하는지 적었는가?

기억할 문장은 짧다.

React 컴포넌트 트리는 설계 구조이고, 플랫폼 UI 트리는 실행 결과다.

다음 글에서는 이 화면 계산을 실행하는 JavaScript와 플랫폼 네이티브 코드가 어떤 책임을 나눠 가지는지 살펴본다.

Active recall

기억에서 꺼내 보기

답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.

  1. 01JSX에 `<PaymentTarget>`이 한 번 나오면 Android·iOS에도 같은 이름의 UI 요소 하나가 생길까?

    정답

    아니다. PaymentTarget은 사용자 컴포넌트다. React는 반환 구조를 계속 계산해 View 같은 호스트 컴포넌트에 도달하고, 플랫폼에는 그 결과에 필요한 호스트 뷰가 반영된다.

    왜 그런가

    사용자 컴포넌트 트리, React Shadow Tree와 최종 플랫폼 UI 트리는 서로 같은 트리가 아니다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  2. 02결제 버튼이 화면에 정상적으로 보이면 PaymentTarget에 전달한 nativeID도 실제 네이티브 대상에 반드시 붙었을까?

    정답

    아니다. 사용자 컴포넌트가 nativeID를 실제 View에 전달하지 않으면 화면은 같아도 네이티브 SDK가 식별자로 대상을 찾지 못한다.

    왜 그런가

    화면 모양과 네이티브 식별자 전달은 별도의 공개 계약이다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  3. 03외부 네이티브 기능이 정확한 자식 뷰 계층을 요구하면 모든 View의 플래트닝을 꺼야 할까?

    정답

    아니다. 정확한 계층이 계약인 해당 View만 collapsable={false}로 보존하고 양쪽 플랫폼의 실제 계층을 확인한다.

    왜 그런가

    nativeID가 있는 View는 현재 문서상 이미 레이아웃 전용 제거 대상이 아니며, 불필요한 View 보존은 최적화를 줄인다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  4. 04React 코드에서 nativeID 전달을 확인하면 양쪽 플랫폼의 SDK 연동까지 증명할 수 있을까?

    정답

    부족하다. 코드에서 props 전달은 확인할 수 있지만, 실제 호스트 뷰와 외부 SDK 대상 탐색은 Android와 iOS에서 각각 확인해야 한다.

    왜 그런가

    렌더러 뒤의 결과가 플랫폼 UI이므로 플랫폼별 계층 검사와 실제 SDK 통합 검증이 별도의 증거다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

출처와 검증 범위

아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.

  1. Render and CommitReact · 공식 문서 · 확인 2026-08-09
  2. Render, Commit, and MountReact Native · 공식 문서 · 확인 2026-08-09
  3. GlossaryReact Native · 공식 문서 · 확인 2026-08-09
  4. Core Components and APIsReact Native · 공식 문서 · 확인 2026-08-09
  5. ViewReact Native · 공식 문서 · 확인 2026-08-09
  6. View FlatteningReact Native · 공식 문서 · 확인 2026-08-09
  7. Set Up Your EnvironmentReact Native · 공식 문서 · 확인 2026-08-09
  8. Get Started Without a FrameworkReact Native · 공식 문서 · 확인 2026-08-09
  9. Extended controls, settings, and helpAndroid Developers · 공식 문서 · 확인 2026-08-09
  10. Running your app on simulated or physical devicesApple Developer · 공식 문서 · 확인 2026-08-09
  11. Debug your layout with Layout InspectorAndroid Developers · 공식 문서 · 확인 2026-08-09
  12. Diagnosing issues in the appearance of a running appApple Developer · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.