렌더링이란
리액트에서 렌더링이란 화면에 특정한 요소를 그려내는 것을 의미합니다. 이 렌더링 과정을 잘 처리해주는 것이 우리가 Vanila JavaScript를 사용하지 않고 React와 같은 UI라이브러리를 사용하는 이유입니다.
브라우저에서 렌더링이란 DOM요소를 계산하고 화면에 그려내는 것을 의미합니다. HTML과 CSS를 가지고 만들어진 DOM과 CSSOM은 서로 결합되어 브라우저에 그려집니다. 그리고 브라우저에서 제공하는 DOM API를 JavaScript를 통해 호출하면서 브라우저에 그려진 화면을 변화시키게 됩니다. 하지만 JavaScript를 이용해서 DOM을 직접 접근하고 수정, 최적화 하는 것은 애플리케이션의 규모가 커질 수록 관리하기가 힘들어집니다.
React는 렌더링 과정을 대신 처리해주고, 개발자는 UI를 설계하는 것이 집중할 수 있도록 해줍니다. 하지만 React 내부에서 처리해주는 렌더링을 최적화 해야 되는 상황이 발생하기도 하는데, 렌더링 과정을 최적화 해주기 위해서는 React 내부에서 렌더링이 언제 발생하고, 어떤 과정을 거쳐서 이루어지는지를 이해하고 있어야 각 과정에서 렌더링을 최적화 할 수 있습니다.
리렌더링은 언제 발생할까?
리액트에서 리렌더링이 되는 시점에 대해 알아보기 전에, 리액트에서 state(상태)이 무엇이고 왜 사용하는지에 대해 알아야 합니다.
리액트에서 state(상태)이란 컴포넌트 내부에서 변할 수 있는 값을 말하는데, UI와 상태(state)를 연동시키기 위해서 사용됩니다. UI와 연동되어야 하고, 변할 여지가 있는 데이터들을 state이라는 형태로 사용하여, 데이터가 변경되었을 때 UI가 그에 맞춰서 변화하기 위해서 state을 변경시키고, state이 변경될 때마다 리렌더링이 됩니다.
즉, 리렌더링이 되기 위해서는 리액트에서 상태(state)가 변경되었다는 것을 알아야 합니다. 리액트가 상태(state)가 변경되었다는 것을 알기 위해서는 setState을 사용해야 합니다. 만약 setState을 사용하지 않고 state을 직접 변경하게 되면 리액트에서 상태(state)가 변경되었다는 것을 인지하지 못할 수도 있어서 리렌더링이 발생하지 않을 수 있습니다.(setState을 사용하는 이유)
특정 컴포넌트의 state이 변한다면, 해당 컴포넌트와 해당 컴포넌트의 하위에 있는 모든 컴포넌트는 리렌더링이 발생하게 됩니다. 이는 리액트를 이용해서 애플리케이션을 설계하고, 최적화하는데 가장 기본이 되는 사항입니다.
리액트의 렌더링 과정
앞서, 리액트는 state가 변화했을 때 리렌더링을 발생시킨다고 했습니다. state(상태)이 변화되고 최종적으로 브라우저상의 UI에 반영되기까지 각 컴포넌트에서는 크게 아래의 4단계를 거치게 됩니다.
- 기존 컴포넌트의 UI를 재사용할 지 확인한다.
- 함수 컴포넌트: 컴포넌트 함수를 호출한다 / Class 컴포넌트: render 메소드를 호출한다.
- 2의 결과를 통해서 새로운 VirtualDOM을 생성한다.
- 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제 변경된 부분만 DOM에 적용한다.
VirtualDOM을 왜 사용하는가?
VirtualDOM은 리액트가 수행하는 최적화 입니다. 리액트에서 VirtualDOM을 사용하는 이유에 대해서 알아보도록 하겠습니다.
먼저 브라우저는 근본적으로 화면을 보여주기 위해서 HTML, CSS, JavaScript를 다운로드 받고 그를 처리해서 화면에 픽셀 형태로 그려냅니다. 그리고 이 과정을 CRP(Critical Rendering Path)라고 부릅니다.
브라우저 렌더링 과정(CRP)는 기본적으로 아래의 과정을 수행합니다.
- HTML을 파싱해서 DOM을 만든다.
- CSS를 파싱해서 CSSOM을 만든다.
- DOM과 CSSOM을 결합해서 Render Tree를 만든다.
- Render Tree와 Viewport의 width를 통해서 각 요소들의 위치와 크기를 계산한다.(Layout)
- 지금까지 계산된 정보를 이용해 Render Tree상의 요소들을 실제 Pixel로 그려낸다. (Paint)
렌더링 이후에 DOM 또는 CSSOM이 수정될 때 마다 또는 UI를 변화하기 위해 DOM을 조작할 때마다 CRP가 수행됩니다. 이는 브라우저에게 많은 연산을 요구하게 되어 퍼포먼스를 저하시키는 요인이 될 수 있습니다. 리액트에서는 CRP가 수행되는 횟수를 최적화 하기 위해서 VirtualDOM을 사용합니다.
리액트에서는 UI의 변화가 발생하면 변화에 필요한 DOM조작을 바로 실제 DOM에 적용하는 것이 아니라, 리액트에서 관리하는 VirtualDOM(DOM과 유사한 객체형태)을 만들어 냅니다. 그리고 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제로 변화가 필요한 DOM 요소를 찾아내어, 한번에 해당 DOM 요소를 조작합니다.
이러한 과정을 통해서 브라우저에서 수행되는 CRP의 빈도를 줄일 수 있습니다.(이는 리액트가 수행하는 최적화입니다.)
내가 할 수 있는 최적화?
VirtualDOM을 통한 최적화는 리액트 내부적으로 수행하기 때문에 개발자 입장에서 따로 최적화를 수행할 필요가 없습니다. 그렇다면 리액트를 사용하는 개발자가 할 수 있는 최적화는 무엇이 있을까?
먼저 부모 컴포넌트가 렌더링 되면서 자식 컴포넌트가 렌더링되지 않고 재사용할지를 확인해야 합니다. 리렌더링 될 컴포넌트의 UI가 이전의 UI와 동일하다고 판단되는 경우 새롭게 컴포넌트를 호출하지 않고 이전의 UI를 그대로 사용하도록 함으로서 최적화를 수행할 수 있습니다.
다음으로는 컴포넌트 함수가 호출되면서 만들어질 VirtualDOM의 형태를 이전의 VirtualDOM과 차이가 적은 형태로 만들어지도록 하는 것입니다. 예를들어 UI를 바꾸기 위해서 <div> 태그를 <span> 태그로 변환시키는 것보다 <div className="block" /> 을 <div className="inline"> 으로 변환시키는 것이 VirtualDOM끼리 비교했을 때 차이가 적은 것을 볼 수 있습니다.
결론
내가 리액트에서 제공하는 useSate 을 사용하고 있으면서 컴포넌트의 렌더링 과정을 모르거나 또는 렌더링 과정을 고려하지 않고 useState을 사용하고 있다면 반성해야한다.
useState 은 단순히 변수를 선언하고 값을 유동적으로 변경해주는 것으로 끝이 아니라 컴포넌트를 다시 렌더링을 시키는 열쇠이다. 그렇기 때문에 아무런 생각 없이 useState 을 마구잡이로 사용하게 되면 생각지 못한 렌더링이 발생되어 내가 원하는데로 작동되지 않을 수 있다.
반대로 useState 을 잘 활용한다면 그만큼 사용자로 하여금 애플리케이션과 원활한 상호작용을 할 수 있도록 만들 수 있다. 뿐만 아니라 내가 작성하는 코드의 질과 유지보수성을 높일 수 있다.
'React' 카테고리의 다른 글
[React] useMemo와 useCallback (0) | 2023.04.26 |
---|---|
[React] React Router (0) | 2023.04.22 |
[React] input의 value 가져오기 (1) | 2023.04.18 |
Router (0) | 2022.07.11 |
State & Props (0) | 2022.07.05 |