React는 Virtual DOM(가상 DOM)을 사용한다. 여기서 DOM(Document Object Model)이란, HTML Document를 트리 구조로 표현한 모델이다. 기존 Vanilla JavaScript의 경우, 실행 환경인 브라우저에서 제공하는 DOM API를 통해 DOM을 조작하고, 이를 브라우저의 렌더링 엔진이 반영하는 방식이다. 따라서 DOM 변경이 발생할 때마다 브라우저는 변경된 DOM을 기반으로 새로운 렌더 트리를 생성하고, 화면에 그리는 높은 비용의 연산을 수행하기 때문에 변경이 자주 발생할 경우 성능 저하를 초래할 수 있다.
반면 React는 상태(State)나 속성(Props)이 변경되면, 메모리에 새로운 Virtual DOM을 생성한다. 변경 전 Virtual DOM과 새로운 Virtual DOM을 비교(Diffing Algorithm)하여 변경된 부분만 찾아 이를 실제 DOM에 적용(Reconciliation)하는데 실제 DOM 업데이트를 최소화하여 성능을 향상시킬 수 있다.
즉, 상태 변경이 발생하면 전체 Virtual DOM을 다시 생성하지만, 실제 DOM에는 변경된 부분만 반영하는 것이 React의 핵심 원리이다.
강의를 듣던 중, 불필요한 렌더링을 방지하기 위해 React.memo()라는 기법이 사용된다는 사실을 접하게 되었는데 처음에는 Virtual DOM에 변화가 없는데 불필요한 렌더링이 발생한다는 것을 이해하지 못했다.
아래 코드를 살펴보면 Parent 컴포넌트에서 State가 변경되었으므로 새로운 Virtual DOM을 만들고, 이전 Virtual DOM과 비교하는 렌더링이 발생하게 된다. 이때, Child 컴포넌트의 Virtual DOM은 변경되지 않았지만 Parent 컴포넌트가 리렌더링됨에 따라 같이 리렌더링된다. React.memo(Child)를 통해 Child 컴포넌트의 props가 변경되지 않을 경우 리렌더링을 방지하여 성능을 최적화할 수 있게 된다.
const Child = () => {
console.log("Child 렌더링")
}
const Parent = () => {
const [count, setCount] = React.useState(0);
console.log("Parent 렌더링")
return (
<div>
<Child />
<button onClick={() => setCount((current) => current + 1)}>Count Up</button>
</div>
);
};
정리하면, Virtual DOM 덕분에 실제 DOM 업데이트는 최소화되지만, 불필요한 Virtual DOM 렌더링은 여전히 발생할 수 있다.