-
[ 모던 리액트 : Deep Dive ] - 02장 리액트 핵심 요소 깊게 살펴보기web 2023. 12. 15. 16:27
🚀 렌더링은 어떻게 일어나는가?
✅ 렌더링이란?
리액트에서의 렌더링 = 리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 UI를 구성하고, 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 과정
브라우저에서의 렌더링 = HTML과 CSS를 기반으로 웹 페이지에 필요한 UI를 그리는 과정
✅ 렌더링은 언제 발생할까?
1. 최초 렌더링 : 유저가 처음 애플리케이션에 진입한 경우
2. 리렌더링 : 최초 렌더링 이후 발생하는 모든 렌더링
1) state가 업데이트 되는 경우
ex. 클래스형 컴포넌트의 setState가 실행되는 경우,
함수형 컴포넌트의 useState() 두번째 배열 요소인 setter가 실행되는 경우
함수형 컴포넌트의 useReducer() 두번째 배열 요소인 dispatch가 실행되는 경우2) props가 업데이트되는 경우
ex. 컴포넌트의 key props가 변경되는 경우3) 부모 컴포넌트가 리렌더링된다면 자식 컴포넌트도 리렌더링
+@ 클래스형 컴포넌트의 forceUpdate가 실행되는 경우 => 리렌더링을 자동으로 실행할 수 없는 경우 강제로 렌더링
✅ key가 필요한 이유
리액트에서 key란? 리렌더링이 발생하는 동안 형제 요소들 사이에서 동일한 요소를 식별하는값
=> 리렌더링이 필요한 컴포넌트를 최소화하기 위해 필요하다! key를 통해 같은 컴포넌트인지를 구분할 수 있기 때문.
🌱 리액트에서 작업을 하다보면 항상 key를 쓰라는 warning이 뜨는 경험을 많이 했다. key는 항상 unique해야하기 때문에 배열의 인덱스를 사용하는 것도 지양하라는 이야기를 많이 들었는데, 이 내용을 읽고 key에 대한 이해가 더 확실해졌다. key가 유니크해야 이 컴포넌트가 다른 컴포넌트임을 알수 있고, 다른 컴포넌트라는 구분이 명확해야 리렌더링이 필요한 컴포넌트가 무엇인지 알 수 있는 것이다!
🌱 또한, key의 변화도 리렌더링을 야기하기 때문에, key를 활용해 강제로 리렌더링을 일으키는 것이 가능하다. 실제로, 리액트에서 무한스크롤을 구현할 때, 다음 페이지를 불러오는 과정이 일어나지 않아서 강제로 key를 랜덤발급받아 강제로 데이터값을 새로 받아온 경험도 있다. 이는은 그리 좋은 방법이 아니었고, 결국 서버에서 hasNext값을 내려주면서 해결했는데, key값이 변경되면 강제 리렌더링이 가능하다는 것을 몸소 깨닫게 해준 경험이었다.
✅ 리액트의 렌더링 프로세스
리액트의 렌더링은 1) 렌더 단계와 2) 커밋 단계로 분리되어있다.
1. 렌더 단계
= 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업
렌더 단계는 컴포넌트를 실행했을 때의 결과와 이전 가상 DOM을 비교하여 변경이 필요한지를 체크하는 과정이다. 여기서는 크게 type, props, key를 비교하고, 변경사항이 있으면 변경이 필요한 컴포넌트로 체크한다.
2. 커밋 단계
= 렌더 단계의 변경사항을 실제 DOM에 적용하는 과정
렌더 단계와 커밋 단계가 끝나야 브라우저의 렌더링이 발생한다.
✔️ 리액트의 렌더링이 일어난다고 해도, 무조건 DOM업데이트가 일어나지는 않는다.
만약 렌더링이 일어났다고 해도 커밋 단계까지 갈 필요가 없다면, 커밋 단계가 생략되어 브라우저의 DOM업데이트가 일어나지 않는다. 즉, 변경사항이 있는지 탐색하는 렌더 단계를 거쳤지만, 변경사항이 없어서 커밋 단계가 실행되지 않는다면 DOM업데이트가 일어나지 않는 것이다.
✔️ 렌더링은 렌더 단계가 종료되어야 커밋 단계가 발생하는, 동기 방식을 보이고 있다.
✅ memo를 사용한다면?
만일 컴포넌트에 memo를 사용한다면 어떻게 될까?
다음은 C컴포넌트의 자식 컴포넌트인 D컴포넌트이다.
const D = () => { return <>지수는 리액트를 공부해</>; };
위의 경우, 부모 컴포넌트인 C컴포넌트가 리렌더링되면 자식 컴포넌트인 D컴포넌트도 함께 리렌더링된다.
const D = memo(() => { return <>지수는 리액트를 공부해</>; });
하지만, 위와 같이 memo로 래핑된 경우에는 렌더 단계에서 컴포넌트 비교를 거치지만, 변경사항이 없으면 렌더링이 생략된다.
🩵 성능 좋은 리액트 웹 애플리케이션을 만들기 위해서는 컴포넌트의 트리 구조를 개선하거나 불필요한 렌더링 횟수를 줄여야한다.
🚀 컴포넌트와 함수의 무거운 연산을 기억해 두는 메모이제이션
메모이제이션은 흔히들 성능 최적화를 위한 기법으로 알려져있고, 이에 대한 여러가지 주장들이 있다.
✅ 주장1 - 섣부른 최적화는 독이다. 꼭 필요한 곳에만 메모이제이션을 추가하자
메모이제이션도 비용이 드는 작업이기 때문에 최적화에는 신중해야한다. 1+1과 같이 가벼운 작업 자체는 메모이제이션하는 것보다 매번 이 작업을 수행하는 것이 더 빠를 수 있다.
✔️ 메모이제이션의 비용
1) 값을 비교하고, 렌더링 또는 재계산이 필요한지 확인하는 작업 비용
2) 결과물을 저장해두었다가 다시 꺼내와야하는 비용
🩵 메모이제이션의 비용이 리렌더링 비용보다 저렴한지 따지고 최적화해야한다.
✅ 주장2 - 렌더링 과정의 비용은 비싸다. 모조리 메모이제이션하자
컴포넌트의 사용에 따라 잘 살펴보고 memo를 일부에만 적용하는 것이 가장 이상적인 상황이겠지만, 리액트 애플리케이션의 규모가 커지고 복잡성이 증가해도 이를 유지할 수 있을까? 최적화와 성능 향상에만 시간을 쏟기 어렵다는 점을 감안하면, 일단 memo로 감싼 뒤 역으로 지불해야하는 비용을 생각해볼 수도 있다.
✔️ 잘못된 memo로 지불해야하는 비용
- props에 대한 얕은 비교가 발생하면서 지불해야하는 비용
✔️ memo를 하지 않았을 때 발생할 수 있는 문제
- 렌더링을 함으로써 발생하는 비용
- 컴포넌트 내부의 복잡한 로직의 재실행
- 위 두가지가 자식 컴포넌트에서 반복
- 리액트가 구 트리와 신규 트리를 비교
즉, memo를 하지 않았을 때의 잠재적 비용이 더 크다는 것을 알 수 있다.
🩵 메모이제이션은 하지 않는 것보다 했을 때 더 많은 이점을 누릴 수 있다.
'web' 카테고리의 다른 글
[ 모던 리액트 : Deep Dive ] - 03장 리액트 훅 깊게 살펴보기 useEffect (0) 2023.12.22 [ 모던 리액트 : Deep Dive ] - 03장 리액트 훅 깊게 살펴보기 useState (1) 2023.12.21 [ 모던 리액트 : Deep Dive ] - 01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트 (2) 2023.12.06 [ 리액트 ] 데이터를 효과적으로 관리하는 방법에 대하여 - 리액트 쿼리와 커스텀 훅 (0) 2023.07.21 리액트 스타일 라이브러리 (StyledComponents, Stitches, Tailwind) 비교 (0) 2023.06.14