Skip to main content
입문React 공통 모델 · 2

React Render와 Commit: 함수 실행과 화면 반영은 어떻게 다른가

컴포넌트가 다시 실행된 사실과 실제 화면 요소가 변경된 사실을 구분하고, 계산 중 외부 작업을 실행할 때 생기는 중복과 잘못된 진단을 설명한다.

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

30초 요약

React DOM 화면 변경을 조사할 때는 Trigger → Render → Commit → Paint를 구분한다.

  1. Trigger: 첫 화면 표시나 상태 변경으로 새 계산이 요청된다.
  2. Render: React가 컴포넌트를 호출해 다음 UI를 계산한다.
  3. Commit: 계산 결과에서 필요한 변경을 브라우저 DOM에 반영한다.
  4. Paint: Web에서는 브라우저가 변경 결과를 픽셀로 그린다.

컴포넌트 함수가 실행됐다는 로그는 Render 호출을 관찰하는 임시 증거다. 실제 DOM이 바뀌었다는 증거는 아니다. 이 차이를 놓치면 성능 문제를 잘못 진단하고, 함수 본문에서 실행한 분석 요청 같은 외부 작업이 사용자 사건과 무관하게 중복될 수 있다.

이 글을 관통하는 상황: 주문 합계 함수가 두 번 실행됐다

결제 화면에서 도움말을 열고 닫는 상태를 부모가 관리한다. 주문 합계의 단가와 수량은 그대로인데, 도움말 버튼을 누를 때마다 OrderTotal의 콘솔 로그도 다시 출력된다.

import { useState } from 'react'
 
function OrderTotal({ unitPrice, quantity }) {
  // 조사 중 함수 호출 여부만 보는 임시 진단이다. 운영 동작으로 남기지 않는다.
  console.count('OrderTotal Render')
  return <strong data-order-total>{unitPrice * quantity}원</strong>
}
 
export default function Checkout() {
  const [showHelp, setShowHelp] = useState(false)
 
  return (
    <section>
      <OrderTotal unitPrice={10000} quantity={2} />
      <button type="button" onClick={() => setShowHelp(!showHelp)}>
        도움말 {showHelp ? '닫기' : '열기'}
      </button>
      {showHelp && <p>결제 전 수량을 확인하세요.</p>}
    </section>
  )
}

“합계 DOM이 매번 두 번 갱신된다”라고 결론 내리면 너무 빠르다. 부모 상태 변경은 새 Render를 요청하고, 기본적으로 React는 그 아래의 OrderTotal도 다시 호출해 결과를 계산할 수 있다. 하지만 단가와 수량이 같아 <strong>의 결과도 같다면 그 요소에 적용할 DOM 변경은 없을 수 있다.

실행 로그만 본 잘못된 판단은 실제 비용의 위치를 흐린다. console.count 자체도 콘솔 출력을 만드는 외부 작업이므로 조사 중 호출 여부를 보기 위한 개발 전용 도구로만 쓰고 제거한다.

더 위험한 경우는 Checkout 함수 본문에서 도움말이 열린 상태만 보고 분석 요청을 보내는 것이다.

if (showHelp) {
  sendHelpOpened()
}

도움말이 열린 뒤 부모의 다른 입력으로 다시 Render되면 사용자가 새로 열지 않았는데도 요청이 반복된다. “현재 열려 있음”이라는 상태와 “사용자가 방금 열었음”이라는 사건을 같은 것으로 본 것이다.

Trigger는 계산을 요청한다

Trigger는 “무엇을 계산할지 다시 확인하라”는 요청이지, 특정 DOM 속성을 즉시 바꾸라는 명령이 아니다. 도움말 상태가 바뀌면 React는 최신 입력으로 Checkout의 다음 UI를 계산해야 한다.

Render는 컴포넌트를 실행해 다음 UI를 계산한다

console.count는 함수가 호출된 횟수를 보여 준다. 따라서 OrderTotal Render: 2OrderTotal이 두 번 계산됐다는 뜻이다. <strong> DOM 노드가 두 번 교체됐다는 뜻은 아니다.

Commit은 필요한 차이만 실제 대상에 반영한다

이 상황에서는 다음 증거를 분리한다.

질문볼 증거
함수가 호출됐는가?조사 중의 임시 개발 로그
Commit된 업데이트의 Render 비용은 얼마인가?React Profiler
합계 DOM 내용이 바뀌었는가?해당 요소에 연결한 MutationObserver 기록
브라우저가 그리기 작업을 했는가?브라우저 개발자 도구의 Performance 기록에 있는 paint 이벤트

브라우저 개발자 도구 Console에서 도움말을 누르기 전에 다음처럼 합계 요소만 관찰할 수 있다.

const total = document.querySelector('[data-order-total]')
const totalChanges = []
const observer = new MutationObserver(records => totalChanges.push(...records))
 
observer.observe(total, {
  childList: true,
  characterData: true,
  subtree: true,
})

도움말을 열고 닫은 뒤 totalChanges.length0이면 관찰을 시작한 이후 합계 요소의 자식·텍스트 변경 기록이 없었다는 증거다. 페이지 전체 DOM이나 브라우저 paint가 없었다는 뜻은 아니다. 조사가 끝나면 observer.disconnect()로 관찰을 중단한다.

Render는 외부 작업의 실행 확정점이 아니다

함수 본문에 다음 코드를 넣으면 현재 상태 계산과 사용자 사건 기록이 섞인다.

function Checkout() {
  const [showHelp, setShowHelp] = useState(false)
 
  if (showHelp) {
    sendHelpOpened()
  }
 
  // ...
}

이 사례의 사건은 도움말이 닫힘에서 열림으로 바뀌는 사용자 클릭이다. 다음 상태와 분석 호출을 같은 처리 함수에 둔다.

function Checkout() {
  const [showHelp, setShowHelp] = useState(false)
 
  function handleHelpToggle() {
    const nextShowHelp = !showHelp
    setShowHelp(nextShowHelp)
 
    if (nextShowHelp) {
      sendHelpOpened()
    }
  }
 
  return (
    <button type="button" onClick={handleHelpToggle}>
      도움말 {showHelp ? '닫기' : '열기'}
    </button>
  )
}

이 코드는 처리 함수가 실행된 한 번의 닫힘→열림 전이마다 분석 함수를 한 번 호출한다. 네트워크 전달 자체가 정확히 한 번 성공한다는 보장은 아니며, 재시도·중복 제거 계약은 별도로 필요하다. 화면이 반영된 뒤 외부 시스템과 동기화해야 하는 작업이라면 5편에서 Effect의 실행 조건과 정리를 설계한다. Render 함수에는 같은 입력에서 같은 UI를 계산하는 코드만 남긴다.

Commit과 브라우저 Paint도 같은 단계가 아니다

React가 DOM 내용을 바꾼 뒤 브라우저는 스타일 계산, 배치와 그리기 같은 자체 렌더링 파이프라인을 수행한다. React Commit 완료는 필요한 DOM 변경을 적용했다는 뜻이지, 사용자가 픽셀을 이미 봤다는 시간 보장은 아니다.

화면이 늦게 보이는 문제를 조사할 때 React Profiler의 Render 시간만으로 결론 내리지 않는다. React 계산이 느린지, DOM 변경 뒤 브라우저 배치·그리기가 느린지 Performance 기록을 함께 본다.

같은 React 계산도 반영 대상은 다르다

따라서 React Native에서도 함수 실행 로그를 실제 플랫폼 UI 요소 변경 횟수로 곧바로 해석하면 안 된다. 반면 브라우저 DOM·paint를 조사하는 방법은 Web 고유이므로 모바일 성능 도구와 구분한다.

같은 상황을 네 단계로 다시 진단한다

문제       도움말을 열 때 OrderTotal 실행 로그도 증가함
잘못된 판단 합계 DOM 변경과 도움말 열기 사건도 Render 수만큼 증가했을 것임
관찰       함수는 다시 실행됐지만 단가·수량과 반환한 합계는 같음
원인       Render 계산 횟수와 Commit 변경 횟수를 같은 것으로 봄
행동       호출·Commit 비용·DOM 변경·paint 증거를 나누고 열기 분석을 클릭 처리로 이동
검증       합계 MutationObserver 기록은 0이고, 닫힘→열림 처리마다 분석 함수가 한 번 호출됨

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

  • 컴포넌트 함수 본문이 UI 계산 밖의 네트워크·저장소·전역 변경을 실행하지 않는가?
  • 임시 Render 로그를 운영 동작이나 사용자 사건의 증거로 남기지 않았는가?
  • 실제 병목을 React 계산, Commit 대상 변경과 브라우저 paint로 나누어 측정했는가?
  • 개발 Strict Mode의 추가 호출을 운영 화면 반영 횟수로 오해하지 않는가?

기억할 축은 짧다.

Render는 계산이다. 실제 반영 단계는 렌더러의 파이프라인까지 확인한다.

다음 글에서는 상태 설정 뒤 React가 이전 상태와 다음 상태를 같은 값으로 판단하는 기준을 살펴본다.

Active recall

기억에서 꺼내 보기

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

  1. 01콘솔에서 컴포넌트 함수 실행을 확인했다면 실제 DOM 요소도 반드시 변경됐다고 결론 내릴 수 있을까?

    정답

    아니다. 함수 실행은 Render 계산의 증거이고, 계산 결과에 실제 차이가 있을 때 필요한 변경이 Commit에서 반영된다.

    왜 그런가

    같은 UI 결과를 다시 계산했다면 함수는 실행됐어도 해당 DOM 요소를 바꿀 작업은 없을 수 있다.

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

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

  2. 02도움말이 열려 있을 때마다 컴포넌트 함수 본문에서 열기 분석 요청을 보내면 왜 위험할까?

    정답

    Render는 사용자 클릭과 일대일 대응하지 않으므로 도움말이 열린 상태에서 다른 Render가 일어나도 요청이 다시 실행될 수 있기 때문이다.

    왜 그런가

    닫힘에서 열림으로 바꾸는 사용자 사건은 해당 클릭 처리 함수에서 기록해야 한다.

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

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

  3. 03React Commit이 끝났다는 말은 브라우저가 픽셀까지 모두 그렸다는 뜻일까?

    정답

    아니다. Commit은 React가 DOM에 필요한 변경을 반영하는 단계이고, 브라우저 paint는 그 뒤 브라우저 렌더링 파이프라인에서 수행되는 별도 작업이다.

    왜 그런가

    React 단계와 브라우저 단계를 구분해야 성능 기록에서 계산, DOM 변경과 실제 그리기를 혼동하지 않는다.

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

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

  4. 04React DOM과 React Native에서 공통인 단계와 달라지는 대상은 무엇인가?

    정답

    컴포넌트 입력으로 다음 UI를 계산하는 Render는 공통이다. React DOM은 Commit에서 DOM을 바꾸지만 React Native New Architecture는 Commit 뒤 Mount에서 실제 플랫폼 UI 요소의 변경을 적용한다.

    왜 그런가

    React 규칙과 각 렌더러의 반영 파이프라인을 분리하면 브라우저 동작을 React Native 규칙으로 잘못 일반화하지 않는다.

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

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

출처와 검증 범위

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

  1. Render and CommitReact · 공식 문서 · 확인 2026-08-08
  2. Components and Hooks must be pureReact · 공식 문서 · 확인 2026-08-08
  3. StrictModeReact · 공식 문서 · 확인 2026-08-08
  4. ProfilerReact · 공식 문서 · 확인 2026-08-08
  5. Learn the BasicsReact Native · 공식 문서 · 확인 2026-08-08
  6. Core Components and APIsReact Native · 공식 문서 · 확인 2026-08-08
  7. Render, Commit, and MountReact Native · 공식 문서 · 확인 2026-08-08
  8. MutationObserverMDN Web Docs · 공식 문서 · 확인 2026-08-08
이 문서의 마지막까지 읽었습니다.