브라우저의 렌더링

브라우저의 렌더링에 대한 설명은 ‘브라우저에 URL을 입력하면 - Render’, ‘Reflow & RePaint - 브라우저 렌더링 최적화’ 포스트를 참고*
React 애플리케이션에서의 렌더링은 브라우저의 성능을 최적화하고 사용자 경험을 개선하는 데 중요한 역할을 한다. DOM 수정을 최소화하는 것은 매우 비용이 높은 연산 과정을 줄이기 위해 필수적이다. 프로젝트의 규모가 커지면 바닐라 자바스크립트만으로는 DOM 수정을 효율적으로 관리하기 어려워지며, React에서는 이를 해결하기 위해
Virtual DOM이라는 프로그래밍 패턴을 사용한다.1. Virtual DOM이란?
Virtual DOM은 메모리 내에 존재하는 가벼운 자바스크립트 객체로, Actual DOM의 구조를 반영한 일종의 사본이다. Virtual DOM은 특정 기술이라기보다는 패턴에 가깝다. React에서의 Virtual DOM이란 UI를 나타내는 객체로 통용되며, React elements와 연관된다. React는 UI의 상태가 변경될 때마다 새로운 Virtual DOM을 생성한다. 변경 후 Virtual DOM과 변경 전 Virtual DOM를 비교(Diffing)해서 변경된 부분만을 효율적으로 찾아내어 Actual DOM에 최소한의 수정만 가하는 방식으로 리렌더링한다.2. React의 렌더링 과정
React의 렌더링 과정은 크게 두 단계로 나눌 수 있다.
렌더 페이즈(Render Phase) → 커밋 페이즈(Commit Phase)
- 렌더 페이즈(Render Phase)
- 렌더 페이즈에서는
Virtual DOM을 생성 및 비교(디핑 Diffing)하는 과정이 이루어진다. 이 단계에서는 실제 DOM에 어떠한 변경도 가해지지 않는다. - React의 함수형 컴포넌트는 코드상으로는 JSX를 리턴하지만, React의 내부 처리 과정을 거쳐
React.Element를 반환한다.React.Element는 화면에 표시할 UI의 설명을 포함한 단순한 객체다. 이 엘리먼트를 기반으로Virtual DOM이 생성된다. - 재조정(Reconciliation): 재조정은 이전
Virtual DOM(Prev VDOM)과 새로운Virtual DOM(Next VDOM)을 비교하여 변경된 부분을 찾아내는 과정이다. 이 과정에서디핑(Diffing)알고리즘이 사용된다. - 트리 재귀(Tree Recursion)
- 두 노드의 타입이 동일한지 확인한다. 예를 들어, 두 노드가 모두
<div>인지,<span>인지 등을 비교한다. 타입이 다르면 이전 노드를 제거하고 새로운 노드를 생성한다. - 노드의 속성(props)을 비교한다. 변경된 속성만
Actual DOM에 반영되게 된다. 예를 들어,<div className="oldClass">에서<div className="newClass">로 변경되면className속성만 업데이트된다. - 자식 노드들을 재귀적으로 비교한다. 이때, 자식 노드의 순서와 구조가 변경되었는지 확인한다.
- 키(Key) 기반 비교 (Keyed Comparison)
- 키가 있는 경우, 리액트는 각 노드를 고유하게 식별할 수 있으므로, 노드의 위치가 변경되거나 새로운 노드가 추가되었을 때 효율적으로 비교할 수 있다. 예를 들어,
<li key="1">, **<li key="2">*와 같이 키가 있는 경우, 리액트는 키를 기준으로 노드를 비교하여 위치가 바뀌었는지, 추가되었는지 등을 판단한다. - 키가 없는 경우, React는 단순히 위치 기반으로 노드를 비교한다. 이는 리스트의 길이나 순서가 변경될 때 비효율적일 수 있다.
예를 들어 아래와 같은 React 컴포넌트는
import React from 'react'; import ReactDOM from 'react-dom'; const element = <h1>Hello, World!</h1>; function App() { return element; } ReactDOM.render(<App />, document.getElementById('root'));
Babel 등의 컴파일러에 의한 JSX 컴파일 과정에서 아래와 같이 변경된다.
import React from 'react'; import ReactDOM from 'react-dom'; const element = React.createElement('h1', null, 'Hello, World!'); function App() { return element; } ReactDOM.render(React.createElement(App, null), document.getElementById('root'));
이후 Webpack 등에 의해 번들링된 코드가 브라우저에서 실행될 때 아래의 객체를 리턴하게 된다. 이 객체, 즉
Virtual DOM 간의 비교(Diffing)를 통해 Actual DOM에서 어떤 부분을 변경할지 판단하게 된다.{ "type": "h1", "key": null, "ref": null, "props": { "children": "Hello, World!" }, "_owner": null, "_store": {} }
디핑(Diffing)
리액트는
Virtual DOM 트리를 재귀적으로 탐색하면서 이전 Virtual DOM과 새로운 Virtual DOM을 비교한다. 이 과정에서 다음과 같은 사항을 확인한다.노드 타입 비교
속성 비교
자식 노드 비교
리스트 구조의 노드들을 비교할 때는 키(key)를 사용하여 효율적으로 비교한다. 키는 각 자식 노드를 고유하게 식별할 수 있는 속성이다.
키가 있는 경우
키가 없는 경우
- 커밋 페이즈(Commit Phase)
- 커밋 페이즈에서는 렌더 페이즈에서 찾은 변경 사항을
Actual DOM에 반영한다. 이 단계에서 리액트는Virtual DOM간의 차이점을Actual DOM에 적용하여 필요한 최소한의 변경만을 수행한다. - 커밋 페이즈는 세 가지 단계로 나눌 수 있다.
- Before Mutation Phase: DOM 업데이트 전 필요한 모든 작업을 수행한다.
- Mutation Phase: DOM 업데이트를 수행한다.
- Layout Phase: 업데이트 후 필요한 후속 작업을 수행한다.
3. Virtual DOM의 성능 최적화
Virtual DOM은 성능 최적화를 위해 다양한 기법을 사용한다.- 배치 업데이트(Batched Updates)
- React는 여러 상태 변경을 하나의 업데이트로 배치 처리하여 불필요한 렌더링을 방지한다. 이는 특히 많은 상태 변경이 짧은 시간 내에 발생할 때 유용하다.
- 메모이제이션(Memoization)
- React에서는 컴포넌트의 렌더링 결과를 메모이제이션하여 동일한 입력에 대해 재사용함으로써 불필요한 렌더링을 방지할 수 있다. 리액트의 **
React.memo*와useMemo,useCallback훅을 사용함으로써 렌더링을 최적화할 수 있는데 이에 대해서는 별도의 포스트에서 다루겠다.
- 키(Key)
- React는 리스트 렌더링 시 각 항목에 고유한 키를 부여하여 변경된 항목만 효율적으로 업데이트할 수 있도록 한다. 키 속성은 디핑 알고리즘이 각 항목을 고유하게 식별하는 데 도움을 준다.