렌더링과 커밋
- Published on
- 리액트에서 렌더링 및 DOM 업데이트 과정을 이해하고, 초기 렌더링과 상태 업데이트를 통한 재렌더링, 그리고 DOM 커밋 단계를 파악합니다.
Table of Contents
컴포넌트가 화면에 표시되기 전에 리액트에 의해 렌더링되어야 합니다. 이 과정에서의 단계를 이해하면 코드가 실행되는 방식을 생각하고 그 동작을 설명할 수 있습니다.
배울 내용
- 리액트에서 렌더링이란 무엇인지
- 컴포넌트가 언제 그리고 왜 렌더링되는지
- 컴포넌트를 화면에 표시하는 과정
- 렌더링이 항상 DOM 업데이트를 유발하지 않는 이유
상상해보세요. 컴포넌트는 주방에서 재료를 사용하여 맛있는 요리를 조리하는 요리사입니다. 이 시나리오에서 리액트는 손님의 요청을 받고 주문을 전달하는 웨이터입니다. UI에 대한 요청과 제공이 이루어지는 과정은 세 가지 단계로 이루어집니다.
- 렌더를 트리거합니다(손님의 주문을 주방에 전달합니다).
- 컴포넌트를 렌더링합니다(주문을 주방에서 처리합니다).
- DOM에 커밋합니다(주문을 테이블에 올려놓습니다).
단계 1: 렌더 트리거
컴포넌트가 렌더링되는 이유는 두 가지입니다.
- 컴포넌트의 초기 렌더링입니다.
- 컴포넌트(또는 그 조상 중 하나)의 상태가 업데이트되었습니다.
초기 렌더링
앱이 시작될 때 초기 렌더링을 트리거해야 합니다. 프레임워크나 샌드박스는 때로 이 코드를 숨깁니다. 그러나 대상 DOM 노드로 createRoot를 호출하고 컴포넌트와 함께 렌더 메서드를 호출함으로써 이를 수행합니다.
root.render()
호출을 주석 처리하고 컴포넌트가 사라지는 것을 확인해보세요!
상태 업데이트에 따른 재렌더링
컴포넌트가 초기 렌더링된 후에는 상태를 업데이트하여 추가적인 렌더링을 트리거할 수 있습니다. set 함수를 사용하여 컴포넌트의 상태를 업데이트하면 자동으로 렌더링이 대기열에 추가됩니다. (각 상태 업데이트마다 고객의 목마름이나 배고픔에 따라 차, 디저트 및 다른 다양한 주문을 넣는 고객처럼 상상해볼 수 있습니다.)
단계 2: 리액트가 컴포넌트를 렌더링
렌더링을 트리거한 후에 리액트는 화면에 표시할 내용을 결정하기 위해 컴포넌트를 호출합니다. "렌더링"은 리액트가 컴포넌트를 호출하는 것입니다.
- 초기 렌더링에서 리액트는 루트 컴포넌트를 호출합니다.
- 추가적인 렌더링에서 리액트는 렌더링을 트리거한 함수 컴포넌트를 호출합니다.
이 프로세스는 재귀적으로 진행됩니다. 업데이트된 컴포넌트가 다른 컴포넌트를 반환하면 리액트는 해당 컴포넌트를 다음으로 렌더링하고, 그 컴포넌트도 무언가를 반환하면 다음 컴포넌트를 렌더링하고, 이런 식으로 계속 진행됩니다. 이 프로세스는 중첩된 컴포넌트가 더 이상 없을 때까지 계속되며, 리액트는 정확히 어떤 내용을 화면에 표시해야 하는지 알게 됩니다.
다음 예제에서 리액트는 Gallery()
와 Image()
를 여러 번 호출합니다.
- 초기 렌더링 중 리액트는
<section>
,<h1>
, 그리고 세 개의<img>
태그에 대한 DOM 노드를 생성합니다. - 재렌더링 중 리액트는 이전 렌더링과 비교하여 변경된 속성을 계산합니다. 이 정보를 다음 단계인 커밋 단계에서 사용하기 전까지는 아무 작업도 수행하지 않습니다.
주의점
렌더링은 항상 순수한 계산이어야 합니다.
- 동일한 입력에 대해 동일한 출력을 반환합니다. 동일한 입력이 주어지면 컴포넌트는 항상 동일한 JSX를 반환해야 합니다. (샐러드에 토마토를 주문한 사람은 양파가 들어간 샐러드를 받아서는 안 됩니다!)
- 자신의 업무에만 집중해야 합니다. 렌더링 이전에 존재한 객체나 변수를 변경해서는 안 됩니다. (한 주문이 다른 사람의 주문을 변경해서는 안 됩니다.)
그렇지 않으면 코드베이스가 복잡해짐에 따라 혼란스러운 버그와 예측할 수 없는 동작을 경험할 수 있습니다. "Strict 모드"에서 개발할 때 리액트는 각 컴포넌트 함수를 두 번 호출하여 비순수 함수에 의한 오류를 파악하는 데 도움이 됩니다.
자세히 알아보기: 성능 최적화
업데이트된 컴포넌트의 모든 중첩된 컴포넌트를 렌더링하는 기본 동작은 트리에서 상위에 위치한 업데이트된 컴포넌트의 성능에는 적합하지 않습니다. 성능 문제가 발생하는 경우, 성능 섹션에 설명된 몇 가지 최적화 방법을 선택적으로 사용하여 문제를 해결할 수 있습니다. 너무 일찍 최적화하지 마세요!
단계 3: 리액트가 DOM에 변경 사항을 커밋
컴포넌트를 렌더링(호출)한 후에 리액트는 DOM을 수정합니다.
- 초기 렌더링에서 리액트는 생성한 모든 DOM 노드를 appendChild() DOM API를 사용하여 화면에 배치합니다.
- 재렌더링에서 리액트는 (렌더링 중 계산된) 최소한의 작업을 수행하여 DOM을 최신 렌더링 결과와 일치하도록 합니다.
리액트는 렌더링 결과가 이전과 동일한 경우에는 DOM 노드를 변경하지 않습니다. 예를 들어, 다음은 부모로부터 전달되는 다른 속성으로 재렌더링되는 컴포넌트입니다. <input>
에 텍스트를 추가하여 값을 업데이트할 수 있지만, 컴포넌트가 재렌더링되어도 텍스트가 사라지지 않습니다.
이 작동하는 이유는 마지막 단계에서 리액트가 <h1>
의 내용만 새로운 시간으로 업데이트하기 때문입니다. JSX에서 <input>
이 동일한 위치에 있음을 알 수 있으므로 리액트는 <input>
또는 그 값을 변경하지 않습니다!
에필로그: 브라우저 페인팅
렌더링이 완료되고 리액트가 DOM을 업데이트한 후에 브라우저는 화면을 다시 그립니다. 이 과정을 "브라우저 렌더링"이라고 알려져 있지만 이 문서에서는 혼동을 피하기 위해 "페인팅"으로 표현합니다.
요약
- 리액트 앱에서 화면 업데이트는 세 단계로 진행됩니다.
- 트리거
- 렌더
- 커밋
- Strict 모드를 사용하여 컴포넌트의 오류를 찾을 수 있습니다.
- 렌더링 결과가 이전과 동일한 경우 리액트는 DOM을 변경하지 않습니다.