렌더링
브라우저에서 렌더링이란 DOM 요소를 계산하고 그려내는 것을 의미한다.
1. HTML과 CSS를 통해 DOM을 만든다.
2. 계산된 DOM은 CSSOM과 결합된다.
3. 위치를 계산한다.
4. 브라우저에 그려진다.
5. 브라우저에서 제공하는 DOM API를 JS를 통해 호출하고, 브라우저에 그려진 화면을 변화시킨다.
Vanila JavaScript를 이용해서 DOM에 직접 접근하고 수정하고(명령형) 최적화하는 것은 애플리케이션 규모가 커질수록 관리하기 힘들다.
그래서 애플리케이션에서 보여주고 싶은 핵심 UI를 선언하기만 하면 실제로 DOM을 조작해서 UI를 그리고, 변화시키는 일은 React와 같은 라이브러리나 프레임워크가 대신해 주는 방법을 사용하게 된 것이다.(선언형)
React에서 리렌더링 되는 시점
리엑트에서 리렌더링이 발생하는 시점은 state가 변했을 때이다.
특정 컴포넌트의 state가 변한다면, 해당 컴포넌트와 해당 컴포넌트 하위에 있는 모든 컴포넌트들은 리렌더링을 하게 된다.
React의 렌더링 과정
state가 변화되고 최종적으로 브라우저상의 UI에 반영되기까지 각 컴포넌트에서는 크게 4단계를 거치게 된다.
1. 기존 컴포넌트의 UI를 재사용할지 확인한다.
2. 함수 컴포넌트: 컴포넌트 함수를 호출한다 / Class 컴포넌트: render 메서드를 호출한다.
3. 2의 결과를 통해서 새로운 Virtual DOM을 생성한다.
4. 이전의 Virtual DOM과 새로운 Virtual DOM을 비교해서 실제 변경된 부분만 DOM에 적용한다.
React를 사용하는 개발자가 할 수 있는 최적화
결론부터 말하면 개발자는 1단계와 3단계를 최적화할 수 있다.(4단계에 해당하는 최적화는 리액트 내부적으로 수행되기 때문이다.)
1. 기존 컴포넌트의 UI를 재사용할지 확인한다.
- 만약 리렌더링 될 컴포넌트의 UI가 이전의 UI와 동일하다고 판단되는 경우
새롭게 컴포넌트 함수를 호출하지 않고 이전의 결괏값을 그대로 사용함으로써 최적화를 수행할 수 있다.
3. 2의 결과를 통해서 새로운 Virtual DOM을 생성한다.
- 컴포넌트 함수가 호출되면서 만들어질 Virtual DOM의 형태를 비교적 차이가 적은 형태로 만들어지도록 하는 것이다.
- 예를 들어, UI를 바꾸기 위해서 <div> 태그를 <span> 태그로 변환시키는 것보다는 <div className="block" />을 <div className="inline">으로 변환시키는 것이 VirtualDOM끼리 비교했을 때 차이가 적은 형태로 만들어지도록 하는 것이다.
그 근거는 Virtual DOM에 있다.
브라우저는 화면을 보여주기 위해서 HTML, CSS, JS를 다운로드하고, 이를 처리해서 화면에 픽셀 형태로 그린다. 이 과정을 CRP(Critical Rendering Path)라고 한다.
1. HTML을 파싱 해서 DOM을 만든다.
2. CSS를 파싱 해서 CSSOM을 만든다.
3. DOM과 CSSOM을 결합해서 Render Tree를 만든다.
4. Render Tree와 Viewport의 width를 통해서 각 요소들의 위치와 크기를 계산한다.(layout)
5. 지금까지 계산된 정보를 이용해 Render Tree 상의 요소들을 실제 Pixel로 그려낸다.(paint)
이후 DOM 또는 CSSOM이 수정될 때마다 위의 과정을 반복한다. 따라서 이 과정을 최적화하는 것이 매우 중요하다. 특히 layout과 paint 과정은 많은 계산을 필요로 하기 때문에 CRP가 수행되는 횟수를 최적화하기 위해서 Virtual DOM을 사용하는 것이다.
1. 기존 컴포넌트의 UI를 재사용할지 확인한다.
리엑트는 state가 변한 경우 해당 컴포넌트와 하위 컴포넌트를 모두 리렌더링 한다.
그런데 state가 변한 컴포넌트의 경우 당연히 리렌더링 해야 하지만,
하위 컴포넌트의 경우에는 props가 변화하지 않았다면 해당 컴포넌트 UI가 변화하지 않았을 수도 있기 때문에 리렌더링 하지 않아도 되는 경우도 있다.
이런 경우에는 굳이 새로운 컴포넌트 함수를 호출하지 않고, 이전에 저장되어 있던 결과를 그대로 사용하는 것이 효율적이다.
하지만 UI가 실질적으로 변화되었는지 안되었는지를 매번 리엑트가 렌더링 과정에서 일일이 모든 컴포넌트 트리를 순회하면서 검사하는 것 은 비효율적이다.
따라서 리엑트에서는 개발자에게 이 컴포넌트가 리렌더링이 되어야 할지 아닐지에 대한 여부를 표현할 수 있는 React.memo 함수를 제공하고, 기존의 컴포넌트의 UI를 재사용할지 판단하는 방법을 사용한다.
React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* render using props */
});
function HOC(Component) {
/* do something */
return <Component />
}
React.memo는 HOC(Higher Order Component)이다. HOC는 컴포넌트를 인자로 받아서 컴포넌트를 리턴하는 컴포넌트이다.
React.memo로 감싸진 컴포넌트의 경우 상위 컴포넌트가 리렌더링 될 경우 무조건 리렌더링 하지 않고,
컴포넌트의 이전 props와 다음 렌더링 때 사용될 props를 비교해서 차이가 있을 경우에만 리렌더링 한다.
만약 차이가 없다면, 기존의 렌더링 결과를 재사용한다.
props를 비교할 때 React.memo는 기본적으로 shallow Compare 하여 판단한다.
만약 기본적인 비교 로직을 사용하지 않고 비교를 판단하는 로직을 직접 작성하고 싶다면, React.memo의 인자로 변화를 판단하는 함수를 두 번째 인자로 넣어주면 된다.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
true를 return할 경우 이전 결과를 재사용
false를 return할 경우 리렌더링을 수행
*/
}
export default React.memo(MyComponent, areEqual);
위의 예제에서 이전의 props와 새로운 props가 인자로 전달되고, return 값이 true일 경우에는 이전 결과를 재사용하고, return 값이 false일 때는 리렌더링을 수행한다.
props를 비교할 때 React.memo는 기본적으로 shallow Compare 하여 판단한다는 것은 다시 말해, props 객체 간을 비교하는 방식을 통해서 동작한다는 것이다.
props 객체는 매 렌더링마다 새롭게 생성되기 때문에 props 자체를 비교하는 것은 의미가 없다.
비교해야 하는 것은 props 객체 안의 각 property이다. 따라서 리엑트는 Object.is() 연산자를 통해서 비교한다.
만약 하나라도 false가 나올 경우 props가 변경되었다고 판단하고 리렌더링을 수행한다.
질문이나 잘못된 점은 댓글로 남겨주세요 :)💖
'React' 카테고리의 다른 글
[React + TS] 무한스크롤 구현하기 (0) | 2023.05.23 |
---|---|
[react] 검색어 입력마다 API가 호출되지 않도록 API 호출 횟수 줄이는 법 (2) | 2023.05.12 |
[react] useId Hook + 예제 (0) | 2023.04.27 |
[react] useCallback Hook + 예제 (0) | 2023.04.27 |
[react] useEffect Hook + 예제 (0) | 2023.04.26 |
댓글