4. 리렌더링
렌더링이란?
현재 props 및 상태를 기반으로 리액트가 컴포넌트에게 UI영역이 어떻게 보이길 원하는지 요청하는 프로세스 이다.
그렇다면 렌더링 프로세스 동안 어떤 일이 일어날까?
먼저 리액트는 가상돔이라는 UI의 가상 표현을 구성
변경 사항이 발생하면 새로운 Virtual DOM 트리 생성
새로운 가상돔을 이전의 가상돔과 비교하여(비교 알고리즘을 통하여) 둘 사이의 차이점을 결정 (reconciliation)
차이점을 기반으로 DOM의 어떤 부분을 업데이트 해야 하는지 식별
변경 사항을 반영하되 실제 DOM에 필요한 업데이트만 적용하고 업데이트
❓변경 사항이란?
상태 변경: 구성 요소의 내부 상태. 일반적으로 useState 훅을 사용하여 정의한 내부 변수이고, 해당 변수의 값이 변경되었을 때를 상태 변경이라 한다. 보통 사용자 상호 작용에 의해서 상태가 변경된다(버튼 클릭 또는 입력 필드 입력)
Props 변경: 부모 컴포넌트로부터 props를 전달받으면, 이것을 기반으로 렌더링을 업데이트 할 수 있다. 리액트는 props가 변경되면 자동으로 컴포넌트를 다시 렌더링한다.
데이터: API를 통해서 서버에서 데이터를 가져오고, 해당 데이터가 구성 요소의 상태를 업데이트 하는 경우 리렌더링하여 화면에 표시
라이프사이클: 마운트, 업데이트, 마운트 해제 등 구성 요소의 라이프사이클이 변경되면 리렌더링 될 수 있다.
❓reconciliation(재조정)
비교 알고리즘을 통하여 이전 가상돔과 새로운 가상돔의 차이점을 발견하고, 차이가 있는 트리의 경우 플래그를 지정한다. 플래그가 지정된 각 컴포넌트에 대해서 리액트는 FunctionComponent(props) 를 호출한다.
클래스 컴포넌트의 경우 classComponentInstance.render() 호출
이러한 비교 및 계산 프로세스를 reconciliation 이라고 한다.
그 후, 자바스크립트가 컴파일되고 배포를 위해 준비될 때 JSX는 React.createElement 호출로 변환된다.
createElement는 UI의 구조를 설명하는 React 요소를 반환한다.
리액트 팀은 이 작업을 개념적으로 다음과 같은 두 단계로 나눈다.
렌더 단계 에서는 컴포넌트 렌더링과 변경 사항 계산
커밋 단계 에서는 렌더 단계에서 계산된 변경 사항을 DOM에 적용
커밋 단계 후 componentDidMount, componentDidUpdate 클래스 라이프 사이클 메서드와 useLayoutEffect 훅을 동기적으로 실행하고, 짧은 시간 제한을 설정하고 이 시간이 만료되면 useEffect 훅을 실행한다.
리액트 18은 useTransition과 같은 "동시 렌더링" 기능을 추가했다. 이를 통해 리액트는 브라우저가 이벤트를 처리할 수 있도록 렌더링 단계에서 작업을 일시 중지할 수 있다.
리렌더링과 관련한 오해
1. props 때문에 리렌더링?
아래와 같은 상황을 가정해보자.
A > B > C > D 인 컴포넌트 트리가 있고 페이지에 표시되어 있다.
사용자는 B에서 카운터를 증가시키는 버튼을 클릭한다.
C, D는 props 로 아무것도 받지 않고 있다.
이 때 렌더링 순서는 B > C > D 이다. 이유는 아래와 같다.
리액트는 트리 최상단에서 렌더 패스를 시작한다.
A가 업데이트가 필요하다고 표시되어 있지 않기 때문에 패스한다.
B가 업데이트가 필요하다고 표시되어 있어 리렌더링 한다. B는 지난번과 같이 <C />를 반환한다.
C는 업데이트가 필요하다고 표시되지 않았지만 부모 B가 렌더링 되었기 때문에, 리렌더링된다. C는 <D />를 다시 반환한다.
D도 부모 C가 렌더링되었기 때문에 리렌더링된다.
B의 상태 값이 변경되었고, C와 D는 상태 값과 무관하지만 리렌더링 된다.
즉, 리렌더링과 props는 관계가 없다.
리렌더링의 요점은 상태 변경이 사용자 인터페이스에 어떻게 영향을 미치는지 파악하는 것이다. 따라서, 정확한 스냅샷(새로운 가상돔)을 얻으려면 잠재적으로 영향을 받을 수 있는 모든 컴포넌트를 다시 렌더링해야 한다.
각 컴포넌트에 useEffect 를 확인한다면?
아래와 같이 표시된다.
이유는 무엇일까?
useEffect의 실행 타이밍
useEffect 훅은 구성요소가 렌더링되거나 업데이트된 후에 실행되지만 반드시 렌더링 단계 직후에 실행되는 것은 아니다. 대신 브라우저가 화면을 그린 후에 실행되도록 예약되어 있어 타이밍 변화가 발생할 수 있다. 리액트는 렌더링 후 useEffect 후크를 일괄적으로 업데이트하고 실행하므로 실행 순서가 항상 구성 요소 계층 구조를 반영하는 것은 아니다.
A 구성요소가 상위 구성요소이고 해당 useEffect 후크가 이론적으로 먼저 실행되어야 하지만 리액트의 일괄 처리 메커니즘은 하위 구성요소 B, C 및 D의 효과가 끝날 때까지 실행을 지연할 수 있다.
그렇다면 리렌더링이 필요없는 컴포넌트에 대해서 메모이제이션 한다면 성능이 올라갈까?
메모이제이션이란 컴포넌트가 다시 리렌더링하지 않도록 제어하는 기술인데, 이 동작은 리엑트가 과거의 스냅샷을 가지고 있기 때문에 가능하다.
그러나 대부분의 경우 컴포넌트를 다시 렌더링하는 것이 리렌더링을 막는 것보다 더 빠를 수 있다. 따라서, 컴포넌트에 많은 props 가 있고 자손이 많은 경우 등의 여러가지 요소를 고려하여 최적화를 하는 것이 바람직하다. 모든 단일 컴포넌트를 메모하는 것은 비생산적이다.
Last updated