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

React Native Fabric: React 계산은 어떻게 실제 화면이 되는가

React가 계산한 UI를 실제 Android·iOS 화면에 반영하는 세 책임을 구분하고, 측정 시점 때문에 안내 상자가 잠깐 잘못된 위치에 나타나는 문제를 교정한다.

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

30초 요약

Fabric 렌더러는 React 계산을 Android·iOS 화면에 바로 명령하지 않는다.

Render → C++ React Shadow Tree를 준비
Commit → 위치·크기를 계산하고 다음 트리를 확정
Mount  → 이전·다음 트리의 차이를 실제 호스트 뷰에 적용

측정한 크기나 위치로 같은 화면을 다시 배치해야 한다면 일반 Effect보다 useLayoutEffect에서 호스트 뷰를 측정한다. 이때 프레임은 사용자가 보는 화면을 한 번 갱신하는 단위다. 공통 파이프라인이 같아도 텍스트 측정과 최종 Mount 결과는 Android와 iOS에서 각각 확인한다.

이 글을 관통하는 상황: 안내 상자가 잠깐 화면 위로 붙는다

결제 버튼 바로 아래에 안내 상자를 놓는 화면을 만든다고 하자. 아래 App.tsx는 버튼의 화면상 위치와 높이를 측정해 안내 상자의 top을 정한다. measure가 전달하는 pageY는 화면 기준 세로 위치이고 height는 측정한 호스트 뷰의 높이다.

import { useEffect, useRef, useState } from 'react'
import { Pressable, StyleSheet, Text, View } from 'react-native'
 
export default function App() {
  const targetRef = useRef<View>(null)
  const [showTip, setShowTip] = useState(false)
  const [tipTop, setTipTop] = useState(0)
 
  function handleToggle() {
    if (!showTip) setTipTop(0)
    setShowTip(value => !value)
  }
 
  useEffect(() => {
    if (!showTip) return
 
    targetRef.current?.measure((_x, _y, _width, height, _pageX, pageY) => {
      setTipTop(pageY + height + 8)
    })
  }, [showTip])
 
  return (
    <View style={styles.screen}>
      <View ref={targetRef} collapsable={false}>
        <Pressable style={styles.button} onPress={handleToggle}>
          <Text style={styles.buttonText}>결제 안내 보기</Text>
        </Pressable>
      </View>
 
      {showTip ? (
        <View style={[styles.tip, { top: tipTop }]}>
          <Text>결제 전에 배송지를 확인하세요.</Text>
        </View>
      ) : null}
    </View>
  )
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 24, paddingTop: 160 },
  button: { backgroundColor: '#222', padding: 16 },
  buttonText: { color: '#fff' },
  tip: {
    position: 'absolute',
    left: 24,
    right: 24,
    backgroundColor: '#ffe9a8',
    padding: 12,
  },
})

이 예제의 collapsable={false}는 1편에서 본 것처럼 측정할 래퍼 호스트 뷰가 최적화로 없어지지 않게 한다. 미리 준비한 가상 증거인 fixture에서는 루트 screen이 화면 좌표의 시작점에 있다고 가정해 pageY를 같은 부모 화면의 절대 위치에 사용한다. 앱의 React 루트가 다른 네이티브 뷰 안에 들어간다면 좌표계를 먼저 맞춰야 한다. 이 글에서 비교할 문제는 측정 대상이나 좌표 변환이 아니라 측정 시점이다.

fixture가 각 Mount와 Effect 실행을 기록했다고 하자. 이것은 React Native의 고정 로그가 아니다.

기록을 보기 전에 예측한다.

  1. showTiptrue가 된 첫 Render에서 tipTop은 얼마인가?
  2. 그 값으로 만든 안내 상자 트리가 먼저 Mount될 수 있는가?
  3. measure 결과로 setTipTop을 호출하면 몇 번째 Render가 필요한가?
0ms   Render A: showTip=true, tipTop=0
2ms   Commit A: 안내 상자 top=0인 다음 트리 확정
4ms   Mount A: 안내 상자를 top=0으로 적용
7ms   일반 Effect: 버튼 측정 결과 top=216
8ms   Render B: showTip=true, tipTop=216
10ms  Commit B와 Mount B: 안내 상자를 top=216으로 이동

기기 부하와 프레임 시점에 따라 사용자가 중간 위치를 실제로 보는지는 달라질 수 있다. 하지만 첫 Mount에 top=0이 들어갔다는 사실은 안정적인 최종 위치만 반영한다는 계약이 없음을 보여 준다.

Fabric은 Render·Commit·Mount를 이어 준다

4편의 JSI는 JavaScript와 C++가 상호작용하는 기반이다. Fabric은 그 기반을 사용해 React 컴포넌트와 C++ Shadow Node를 연결하지만, JSI 자체와 같은 이름의 기능은 아니다.

Render는 다음 화면의 구조를 계산한다

state가 바뀐 업데이트에서는 모든 노드를 무조건 새로 만들지 않는다. 변경 경로의 Shadow Node를 복제하고 바뀌지 않은 부분은 재사용할 수 있다. 이 단계의 결과는 다음 플랫폼 화면을 위한 계산 구조이지 아직 사용자가 보는 Android View나 iOS UIView 변경 그 자체가 아니다.

Commit은 레이아웃과 다음 트리를 확정한다

대부분의 레이아웃 계산은 C++에서 이루어지지만 텍스트와 입력처럼 플랫폼 고유 크기가 필요한 컴포넌트는 Android·iOS가 제공하는 측정 함수의 도움을 받는다. 따라서 공통 스타일 입력이 같다는 사실만으로 양쪽 플랫폼의 모든 픽셀이 같다고 결론 내리지 않는다.

Mount는 계산된 차이를 실제 화면에 적용한다

1편의 View Flattening도 이 차이 계산 구간에 참여해 필요 없는 호스트 뷰 생성을 줄일 수 있다. 따라서 setShowTip(true) 한 줄을 플랫폼 뷰를 직접 움직이는 명령으로 해석하면 안 된다.

setShowTip(true)
  → React가 다음 element 계산
  → Fabric이 Shadow Tree 준비
  → Yoga가 위치·크기 계산, 다음 트리 확정
  → 이전·다음 트리 차이 계산
  → UI 스레드가 실제 호스트 뷰에 변경 적용

측정이 필요한 UI는 Layout Effect에서 닫는다

useLayoutEffect는 React가 화면 변경을 확정한 뒤 일반 Effect보다 먼저 레이아웃을 읽고 필요한 상태 변경을 마칠 때 사용하는 Hook이다. Hook은 컴포넌트에서 React 기능을 사용하는 함수다.

관통 예제에서는 파일의 두 부분만 정확히 교체한다.

- import { useEffect, useRef, useState } from 'react'
+ import { useLayoutEffect, useRef, useState } from 'react'
 
- useEffect(() => {
+ useLayoutEffect(() => {
    if (!showTip) return
 
    targetRef.current?.measure((_x, _y, _width, height, _pageX, pageY) => {
      setTipTop(pageY + height + 8)
    })
  }, [showTip])

같은 fixture에서 기대할 교정 기록은 다음과 같다.

0ms  Render A: showTip=true, tipTop=0
2ms  Commit A: 안내 상자 top=0인 다음 트리 확정
3ms  Mount A: 측정 가능한 호스트 뷰에 top=0 적용, 아직 다음 화면을 표시하기 전
4ms  Layout Effect: 버튼 측정 결과 top=216, setTipTop(216)
5ms  Render B와 Commit B: 안내 상자 top=216인 다음 트리 확정
6ms  Mount B: 같은 화면 표시 전에 안내 상자를 top=216으로 다시 적용
7ms  사용자에게 표시된 첫 화면: 안내 상자 top=216

이 기록도 실제 구현이 자동으로 출력하는 보장이 아니다. 검증할 성공 조건은 첫 화면 캡처부터 안내 상자가 버튼 아래에 있고 top=0인 중간 화면이 한 프레임도 기록되지 않는 것이다. Effect의 실행 횟수만 세지 말고 실제 Mount 결과를 Android와 iOS에서 각각 관찰한다.

공통 파이프라인과 플랫폼 결과를 나눠 검증한다

이 안내 상자는 다음 순서로 검증한다.

  1. Android emulator에서 안내 상자를 열고 첫 프레임부터 버튼 아래인지 기록한다.
  2. iOS simulator에서 같은 절차와 긴 글자·큰 글꼴 설정을 반복한다.
  3. 두 플랫폼에서 안내 상자를 닫았다 다시 열어 이전 측정값에 우연히 의존하지 않는지 확인한다.
  4. 실제 지원 화면 크기와 글꼴 크기 조합에서 잘림과 겹침이 없는지 확인한다.

이 순서와 위치 판단은 simulator·emulator에서 재현할 수 있다. 카메라·센서처럼 실제 하드웨어가 필요한 기능은 아니므로 이 글의 Fabric 단계 검증에 실기기를 필수 조건으로 두지 않는다. 다만 제품이 별도 실기기 화면 품질 기준을 갖고 있다면 그 지원 기기에서도 최종 결과를 확인한다.

같은 상황을 다시 진단한다

문제       안내 상자가 처음에는 top=0에 Mount되고 측정 뒤 버튼 아래로 이동함
잘못된 판단 setState가 Android·iOS 뷰를 그 자리에서 직접 움직이므로 Effect 시점은 무관함
관찰       첫 Mount 뒤 일반 Effect가 측정하고 두 번째 Render·Commit·Mount를 만듦
원인       실제 레이아웃에 의존하는 상태 갱신이 일반 Effect까지 늦어짐
행동       호스트 뷰 측정과 위치 갱신을 useLayoutEffect에서 수행
검증       양쪽 플랫폼의 첫 기록부터 최종 위치이며 top=0 중간 Mount가 노출되지 않음

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

  • React Render 결과를 플랫폼 뷰 직접 변경 명령처럼 설명하지 않았는가?
  • Commit의 레이아웃·트리 확정과 Mount의 실제 호스트 뷰 변경을 구분했는가?
  • 측정 대상이 실제 호스트 뷰로 남고 최신 레이아웃을 읽는가?
  • 측정 기반 UI가 첫 프레임부터 최종 위치에 있는가?
  • Android와 iOS의 측정·최종 화면 결과를 각각 확인했는가?

기억할 문장은 짧다.

Fabric은 React 계산을 Shadow Tree, 레이아웃, 차이 적용 순서로 실제 화면에 옮긴다.

다음 글에서는 화면이 아닌 플랫폼 기능을 JavaScript에 노출하는 TurboModules의 책임을 살펴본다.

Active recall

기억에서 꺼내 보기

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

  1. 01컴포넌트 함수가 새 JSX를 반환하는 순간 Android View나 iOS UIView도 그 자리에서 바로 바뀔까?

    정답

    아니다. Fabric이 Shadow Tree 생성, 레이아웃 계산과 트리 확정, 차이 계산과 UI 스레드의 Mount를 거쳐 실제 호스트 뷰에 반영한다.

    왜 그런가

    React 계산 결과와 플랫폼 화면 변경 사이의 단계를 나눠야 중간 레이아웃과 측정 시점을 올바르게 조사할 수 있다.

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

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

  2. 02Commit이 끝났다는 말은 Android·iOS 호스트 뷰 변경까지 모두 적용됐다는 뜻일까?

    정답

    아니다. Commit은 레이아웃을 계산하고 다음 Shadow Tree를 확정하며, Mount가 이전·다음 트리의 차이를 실제 호스트 뷰에 적용한다.

    왜 그런가

    React 공통 모델의 Commit과 React Native 문서가 구분하는 Mount를 같은 단계로 합치면 실제 화면 변경 시점을 잘못 읽는다.

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

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

  3. 03호스트 뷰 크기로 안내 상자 위치를 정할 때 일반 Effect에서 측정해도 첫 중간 위치가 절대 생기지 않을까?

    정답

    그렇게 보장할 수 없다. 일반 Effect를 기다리는 동안 기본 위치를 가진 트리가 먼저 Mount될 수 있으므로, 최신 레이아웃 측정과 같은 프레임의 교정에는 useLayoutEffect를 사용한다.

    왜 그런가

    측정값이 필요한 UI는 측정 시점과 그 결과로 생기는 두 번째 상태 갱신까지 하나의 검증 흐름으로 본다.

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

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

  4. 04공통 Fabric 단계가 같으면 한 플랫폼에서 맞는 안내 상자 위치를 다른 플랫폼에서도 그대로 보장할 수 있을까?

    정답

    아니다. 파이프라인은 공통이지만 텍스트 같은 일부 측정과 Mount 구현은 호스트 플랫폼에 의존하므로 Android와 iOS 결과를 각각 확인한다.

    왜 그런가

    공통 렌더러 구조와 플랫폼별 최종 픽셀 결과는 서로 다른 검증 범위다.

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

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

출처와 검증 범위

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

  1. Render, Commit, and MountReact Native · 공식 문서 · 확인 2026-08-09
  2. Threading ModelReact Native · 공식 문서 · 확인 2026-08-09
  3. Cross-Platform Architecture GlossaryReact Native · 공식 문서 · 확인 2026-08-09
  4. Measuring the LayoutReact Native · 공식 문서 · 확인 2026-08-09
  5. useLayoutEffectReact · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.