본문 바로가기
목차
[노트장] 적으며 정리해 보는 이론/리액트문서

[리액트문서보기][상호작용성 더하기] 렌더링 그리고 커밋

by 졸린부엉이 2024. 3. 26.

학습내용

  • React에서 렌더링의 의미
  • React가 컴포넌트를 언제, 왜 렌더링 하는지
  • 화면에 컴포넌트를 표시하는 단계
  • 렌더링이 항상 DOM 업데이트를 하지 않는 이유

 

 


 

랜더링 

React와 같은 라이브러리 또는 프레임워크에서 "렌더링"은 UI 요소를 생성하고 이를 화면에 표시하는 과정을 의미합니다.

UI요소를 생성하는 과정은 사용자가 보는 웹 페이지나 어플리케이션의 구성 요소를 만드는 과정을 의미합니다. 이것은 HTML 요소, CSS 스타일, 그리고 JavaScript 코드를 사용하여 웹 페이지의 레이아웃과 디자인을 결정하는 것을 포함합니다.

또한, 컴포넌트의 상태나 속성이 변경되면, React는 해당 컴포넌트의 JSX를 다시 렌더링하여 새로운 UI를 생성하며,

새로운 UI를 생성할 때 기존 UI는 메모리에서 사라지게 됩니다

 

React는 Virtual DOM이라는 메커니즘을 사용하여 이전 UI와 새로운 UI를 비교하고, 변경된 부분만을 실제 DOM에 적용합니다. 따라서 새로운 UI가 생성되면 React는 이전 UI를 버리고 새로운 UI로 대체합니다. 그리고 사용자는 이 변경된 UI를 볼 수 있게 됩니다.

 

 

[+중요]

style 속성을 변경하는 것은 React에서 재랜더링(rendering)이나 페인팅(painting)과 직접적으로 관련이 없습니다.

// 랜더링을 일으키는 style 변경 코드
document.querySelector('.class').style.color='red';​


React에서 렌더링이란 가상 DOM을 업데이트하여 실제 DOM에 반영하는 것을 의미합니다. 이는 상태나 프로퍼티의 변경에 따라 컴포넌트 트리를 재구성하고, 이에 따라 실제 DOM이 변경될 수 있음을 의미합니다. 이러한 변경은 React의 렌더링 프로세스에서 처리됩니다.

한편, 페인팅은 실제로 브라우저가 요소의 스타일이나 내용이 변경되었을 때 화면을 다시 그리는 과정을 의미합니다. 페인팅은 브라우저가 직접 처리하는 과정이며, React는 이와 관련하여 직접 개입하지 않습니다.

따라서 style 속성을 변경하는 것은 React에서는 렌더링이나 페인팅과 직접적으로 관련이 없습니다. 그것은 단순히 DOM 요소의 스타일을 변경하는 것이며, 브라우저에서 페인팅 과정을 유발할 수 있습니다.

 

 

 

React나 다른 프론트엔드 라이브러리/프레임워크에서 UI 요소를 생성하는 과정은 일반적으로 다음과 같습니다

  1. 컴포넌트 작성: UI를 구성하는 각각의 블록을 컴포넌트로 작성합니다. 이 컴포넌트들은 보통 JavaScript 함수나 클래스로 정의됩니다.
  2. JSX 작성: 각 컴포넌트 안에서 JSX 문법을 사용하여 해당 컴포넌트의 UI를 설명합니다. JSX는 JavaScript의 확장 문법으로, HTML과 비슷하게 생겼지만 JavaScript 코드 안에서 사용됩니다.
  3. 컴포넌트 조립: 상위 수준의 컴포넌트에서 하위 수준의 컴포넌트를 조합하여 전체 UI를 구성합니다. 이것은 부모 컴포넌트가 자식 컴포넌트를 포함하고 필요한 props를 전달하는 과정입니다.
  4. 렌더링: 최상위 컴포넌트를 ReactDOM.render()나 유사한 메서드를 사용하여 실제 DOM에 렌더링합니다. 이 과정에서는 가상 DOM에 생성된 구성 요소가 실제 DOM 요소로 변환되어 웹 페이지에 표시됩니다.

 

 


 

 

 

 

주방에서 요리사가 컴포넌트를 재료로 맛있는 요리를 한다고 상상해보세요.
이 시나리오에서 React는 고객들의 요청을 받고 주문을 가져오는 웨이터입니다.
이 과정에는 UI를 요청하고 제공하는 세 가지 단계가 있습니다.

렌더링 트리거 (손님의 주문을 주방으로 전달)
컴포넌트 렌더링 (주방에서 주문 준비하기)
DOM에 커밋 (테이블에 주문한 요리 내놓기)

 

 

 1단계: 랜더링 트리거  

 

컴포넌트 렌더링이 일어나는 데에는 두 가지 이유가 있습니다.

  1. 컴포넌트의 초기 렌더링인 경우
  2. 컴포넌트의 state가 업데이트된 경우 [+추가 정보]
[+ 추가 정보]  (+ 변경 24.03.27)

1. 자식 state 스스로가 데이터가 변경 될떄는 랜더링이 됩니다.

2. 부모(state 값 변경, 자식 에서 props 전달) => 자식 (props받아 사용)  // 부모, 자식 렌더링됨
부모 컴포넌트에서 상태가 변경되었을 때, 해당 상태를 props로 전달받아 사용하는 자식 컴포넌트만 다시 렌더링되고, 값이 변경되지 않은 다른 자식 컴포넌트는 렌더링되지 않습니다.

3. 부모(state 값 변경, 자식 에서 props 전달 안함) => 자식 (props받지 않음) // 부모만 렌더링
부모 컴포넌트가 렌더링될 때, 해당 부모 컴포넌트 내부에 있는 모든 자식 컴포넌트도 렌더링되지만, 자식 컴포넌트의 props가 변경되지 않으면 해당 자식 컴포넌트는 재랜더링되지 않습니다.

 

 

예시 주의 (+ 추기24.03.28)

function Test() {
	console.log('자식 컴포넌트');
	return <div> 자식 </div>
}

function Main() {
	  const [num, setNum] = useState(0);
      const plus = () => {
      	setNum(prev=>prev++);
      }
	return <div> 
    	{num}
    	<Test/>
        <button onClick={plus}>증가</button>
    </div>
}


Main에서 증가 버튼을 클릭하게 된다면 렌더링이 되어 Test에 있는 console.log('자식 컴포넌트');는 계속 출력이 될것이다. 이것은 자식 컴포넌트도 리렌더링이 되었다라는 것 보다는, Main이 호출이 되면서 자식인 <Test/> 컴포넌트 함수를 호출하게 되어 그 안에 있는 console.log를 실행 한것이다.

React의 리렌더링 동작과는 무관하며, 리렌더링의 결과물이 실제 DOM에 반영되는 것은 아닙니다. 즉, 화면에는 출력되지 않고, 개발자 도구의 콘솔에만 표시됩니다.

즉, console.log가 계속 출력되는 것은 React의 컴포넌트 렌더링 동작과는 무관하며, 해당 컴포넌트의 함수가 실행되는 JavaScript의 일반적인 동작입니다.

 

 


 

초기 렌더링

앱을 시작할 때 초기 렌더링을 트리거해야 합니다. 프레임워크와 샌드박스는 때때로 이 코드를 숨기곤 하지만, 대상 DOM 노드와 함께 createRoot를 호출한 다음 해당 컴포넌트로 render 메서드를 호출하면 이 작업이 완료됩니다.

import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />);

 

 

 

 

State 업데이트 시 리렌더링

컴포넌트가 처음으로 렌더링 된 후에는 set 함수를 통해 상태를 업데이트하여 추가적인 렌더링을 트리거할 수 있습니다. 컴포넌트의 상태를 업데이트하면 자동으로 렌더링 대기열에 추가됩니다. (이것은 레스토랑의 손님이 첫 주문 이후에 갈증이나 배고픔의 상태에 따라 차, 디저트 등의 메뉴를 주문하는 것으로 상상해 볼 수 있습니다.)

 

 

 

 


 

 

 

2단계: React 컴포넌트 렌더링  

렌더링을 트리거한 후 React는 컴포넌트를 호출하여 화면에 표시할 내용을 파악합니다. “렌더링”은 React에서 컴포넌트를 호출하는 것입니다.

  • 초기 렌더링에서 React는 루트 컴포넌트를 호출합니다.
  • 이후 렌더링에서 React는 state 업데이트가 일어나 렌더링을 트리거한 컴포넌트를 호출합니다.

재귀적 단계: 업데이트된 컴포넌트가 다른 컴포넌트를 반환하면 React는 다음으로 해당 컴포넌트를 렌더링하고 해당 컴포넌트도 컴포넌트를 반환하면 반환된 컴포넌트를 다음에 렌더링하는 방식입니다. 중첩된 컴포넌트가 더 이상 없고 React가 화면에 표시되어야 하는 내용을 정확히 알 때까지 이 단계는 계속됩니다.

 

 

성능 최적화

업데이트된 컴포넌트 내에 중첩된 모든 컴포넌트를 렌더링하는 기본 동작은 업데이트된 컴포넌트가 트리에서 매우 높은 곳에 있는 경우 성능 최적화되지 않습니다. 성능 문제가 발생하는 경우 성능 섹션에 설명된 몇 가지 옵트인 방식으로 문제를 해결 할 수 있습니다. 성급하게 최적화하지 마세요!

왜??

최적화를 하지 말라는 것은 최적화가 항상 필요하지 않을 수 있고, 성급하게 최적화하면 코드를 복잡하게 만들고 유지보수를 어렵게 할 수 있다는 것을 의미합니다.

React는 성능을 향상시키기 위해 이미 여러 가지 내부적인 최적화를 수행하고 있습니다. 예를 들어, React는 Virtual DOM을 사용하여 실제 DOM 조작을 최소화하고, PureComponent와 memo를 사용하여 불필요한 렌더링을 방지합니다.

따라서, 대부분의 경우 React 애플리케이션은 성능에 큰 문제가 없을 수 있습니다. 성능 문제가 발생하는 경우에는 React의 내장 도구를 활용하여 최적화를 진행하거나, 성능 문제의 근본 원인을 파악하여 해결하는 것이 좋습니다. 또한, 성능 최적화는 개발 초기 단계에서보다는 실제 성능 문제가 발생한 후에 진행하는 것이 좋습니다.

따라서, 성능 최적화를 하지 않더라도 대부분의 경우에는 React 애플리케이션이 충분히 빠르게 동작할 수 있으며, 너무 성급하게 최적화에 집착할 필요는 없다는 것을 의미합니다.

 

 


 

 

3단계: React가 dom에 변경사항을 커밋  

컴포넌트를 렌더링(호출)한 후 React는 DOM을 수정합니다. ( 렌더링과 DOM 수정은 다른것임 )

  • 초기 렌더링의 경우 React는 appendChild() DOM API를 사용하여 생성한 모든 DOM 노드를 화면에 표시합니다.
  • 리렌더링의 경우 React는 필요한 최소한의 작업(렌더링하는 동안 계산된 것!)을 적용하여 DOM이 최신 렌더링 출력과 일치하도록 합니다.

React는 Virtual DOM을 사용하여 실제 DOM 조작을 최소화합니다. 렌더링 간에 React는 이전 상태와 현재 상태를 비교하여 실제 DOM을 업데이트해야 할 필요가 있는지 여부를 결정합니다. 이전 상태와 현재 상태가 동일하면 React는 DOM을 변경하지 않습니다.

예를 들어, 부모 컴포넌트가 자식 컴포넌트에게 props를 전달하고, 이 props가 변경되었을 때 자식 컴포넌트가 다시 렌더링된다고 가정해 봅시다. 그러나 자식 컴포넌트가 다시 렌더링되더라도 DOM의 나머지 부분은 변경되지 않는다면, React는 DOM을 다시 그리지 않고 이전 상태와 현재 상태의 차이를 파악하여 필요한 부분만 업데이트합니다. 이를 통해 React는 불필요한 DOM 조작을 피하고 성능을 최적화할 수 있습니다.

즉, React는 렌더링 간에 차이가 있는 경우에만 DOM 노드를 변경하여 최신 상태와 일치하도록 합니다. 변경 사항이 없는 경우에는 DOM을 다시 그리지 않습니다.

 

 

 


 

 

 

에필로그: 브라우저 페인트  

렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 다시 그립니다. 이 단계를 “브라우저 렌더링”이라고 하지만 이 문서의 나머지 부분에서 혼동을 피하고자 “페인팅”이라고 부를 것입니다.

React 애플리케이션이 렌더링되고 DOM이 업데이트되면, React는 실제 DOM 조작을 수행합니다. 변경된 요소만 업데이트하고, 나머지는 건너뛰어서 성능을 향상시킵니다. 그리고 이후에 브라우저는 업데이트된 DOM을 기반으로 화면을 다시 그리는데, 이를 "페인팅"이라고 합니다. 따라서 React는 DOM을 업데이트하고, 그 후에 브라우저가 업데이트된 DOM을 화면에 다시 그립니다.

 
 
 

 

 

 

 

브라우저 랜더링 프로세스

일반적으로 브라우저는 HTML, CSS 및 JavaScript로 구성된 웹 페이지를 표시합니다. 이러한 페이지를 브라우저에서 표시하기 위해서는 몇 가지 단계를 거쳐야 합니다.

DOM 생성: HTML 문서를 파싱하여 DOM(Document Object Model) 트리를 생성합니다. 이 트리는 문서의 구조를 표현하는데, 각 요소(태그)는 DOM 노드로 변환됩니다.

CSSOM 생성: 브라우저는 CSS 파일을 가져와 CSS Object Model(CSSOM)을 생성합니다. CSSOM은 각 요소에 적용되는 스타일 정보를 포함합니다.

렌더링 트리 생성: DOM과 CSSOM이 결합되어 렌더링 트리(Render Tree)가 형성됩니다. 이 트리는 실제로 화면에 표시되는 요소만을 포함하며, 레이아웃에 사용되는 요소와 스타일 정보를 결합합니다.

레이아웃: 렌더링 트리를 기반으로 각 요소의 위치와 크기를 계산하여 브라우저의 뷰포트에 배치합니다. 이를 레이아웃(Layout) 또는 리플로우(Reflow)라고 합니다.

페인팅: 렌더링된 요소들을 실제 화면에 그리는 단계입니다. 렌더링 엔진은 렌더링 트리를 순회하면서 각 요소를 픽셀로 변환하고 화면에 표시합니다.

 

 

[리플로우와 리페인트 참고]
https://mong-blog.tistory.com/entry/%EB%A6%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%A6%AC%ED%8E%98%EC%9D%B8%ED%8A%B8%EC%99%80-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0


 

 

◼ 요약 

  • React 앱의 모든 화면 업데이트는 세 단계로 이루어집니다.
    1. 트리거
    2. 렌더링
    3. 커밋
  • Strict Mode를 사용하여 컴포넌트에서 실수를 찾을 수 있습니다.
  • 렌더링 결과가 이전과 같으면 React는 DOM을 건드리지 않습니다.

 

 

 

[참고]

https://ko.react.dev/learn/render-and-commit

https://www.youtube.com/watch?v=AnyQkBvgG90&list=PLjQV3hketAJkh6BEl0n4PDS_2fBd0cS9v&index=17