React 렌더링은 어떻게 일어나는가

Date
Created
May 9, 2024 12:15 PM
Tags
2장

React의 렌더링이란?

렌더링이라는 용어는 브라우저에서도 사용하기 때문에 리액트에서의 렌더링과 구분할 필요가 있다.
리액트의 렌더링은 리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 자신이 가진 props와 state의 값을 기반으로 UI를 생성하고 어떤 DOM 결과를 브라우저에게 제공할 것인지 계산하는 일련의 과정을 의미함.
컴포넌트가 props와 state를 가지지 않는다면 오직 컴포넌트가 반환하는 jsx값에 기반해 렌더링이 일어남.

React의 렌더링이 일어나는 이유

렌더링은 언제 일어나는가
  1. 최초 렌더링: 사용자가 처음 애플리케이션에 진입한 경우.
  1. 리렌더링: 최초 렌더링 이후의 모든 렌더링 작업
    1. 컴포넌트의 useState()의 setter가 작동하는 경우
    2. useReducer()의 두 번째 배열 요소인 dispatch가 실행되는 경우
    3. 컴포넌트의 key props가 변경되는 경우
    4. 👀
      컴포넌트를 배열로 렌더링하는 경우 key를 제공하지 않으면 경고가 출력되는데 왜 key가 필요할까? key는 리렌더링이 발생하는 동안 형제 요소 사이에 동일한 요소를 식별하는 값. 리액트 파이버는 동일 자식 컴포넌트가 여러개 있는 구조에서 리렌더링이 발생하면 current 트리와 workInProgress 트리 사이에 어떤 컴포넌트가 변경이 있었는지 구분해야 하는데. 두 트리 사이에서 같은 컴포넌트인지 구별하는 값이 key. 변경사항을 구별하는 작업은 리렌더링이 필요한 컴포넌트를 최소화 해야 하므로반드시 필요한 작업. key가 존재한다면 두 트리 사이에 통일한 key를 가진 컴포넌트는 이를 기준으로 구별할 수 있지만 key가 없다면 파이버 내부의 sibling 인덱스를 기준으로 판단하게 된다.
위의 이유가 리액트의 렌더링을 발생시키는 유일한 시나리오.
useState()로 관리되지 않는 변수는 변경된다 하더라도 리렌더링을 발생시키지 않아 화면에서 확인할 수 없다.

리액트의 렌더링 프로세스

리액트의 렌더링 프로세스가 시작회면 리액트는 컴포넌트의 루트에서부터 가장 아래까지 업데이트가 필요로하는 모든 컴포넌트를 찾는다.
여기서 업데이트가 필요하다고 지정된 컴포넌트를 발견하면 함수형 컴포넌트는 FunctionComponent() 그 자체를 호출하고, 그 결과물을 저장한다.
일반적으로 렌더링 결과물은 jsx 문법으로 구성돼 있고, 이것이 자바스크립트로 컴파일 되면서 React.createElement() 를 호출하는 구문으로 변환됨.
여기서 createElement는 브라우저의 UI 구조를 설명할 수 있는 일반적인 자바스크립트 객체를 반환함.
function Hello(){ return ( <TestComponent a={35} b='yceffort'> 안녕하세요 </TestComponent> ) }
function Hello(){ return React.createElement( TestComponent, { a: 35, b: 'yceffort' }, '안녕하세요' ) }
{type: TestComponent, props: { a: 35, b: 'yceffort', children: '안녕하세요'}}
렌더링 프로세스가 실행되며 이런 과정을 거쳐 각 컴포넌트의 렌더링 결과물을 수집하고 리액트의 새로운 트리인 VOM과 비교해 실제 DOM에 반영하기 위한 모든 변경 사항을 차례차례 수집함.
리액트의 렌더링은 렌더 단계와 커밋 단게라는 총 두 단계로 분리되어 실행됨.

렌더

렌더 단계는 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업.
즉, 렌더링 프로세스에서 컴포트를 실행해(return) 이 결과와 이전 VDOM을 비교하는 과정을 거쳐 변경이 필요한 컴포넌트를 체크하는 단계.
비교하는건 크게 세 가지로, type, props, key 이다. 이 세 가지 중 하나라도 변경된 것이 있다면 변경이 필요한 컴포넌트로 체크해둔다.

커밋

커밋 단계는 렌더 단계의 변경 사항을 실제 DOM에 적용해 사용자에게 보여주는 과정.
이 단계가 끝나야 비로소 브라우저의 렌더링이 발생함.
리액트가 DOM을 커밋 단계에서 업데이트 하면 이렇게 만들어진 모든 DOM 노드 및 인스턴스를 가르키도록 리액트 내부의 참조를 업데이트함. 함수형 컴포넌트는 useLayoutEffec 훅이 호출된다.
 
여기서 알 수 있듯 리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아님.
렌더링을 수행했으나 커밋 단계까지 갈 필요가 없다면, 즉 변경 사항을 계산했는데 아무런 변경 사항이 갑지되지 않는다면 이 커밋 단계는 생략될 수 있다.즉 리액트의 렌더링은 꼭 가시적인 변경이 일어나지 않아도 발생할 수 있음.
렌더링 과정 중 첫 번째 단계인 렌더 단계에서 변경 사항을 감지할 수 없다면 커밋 단계가 생략되어 브라우저의 DOM업데이트가 일어나지 않을 수 있음.

동기식 작동

이 두 가지 과정으로 이뤄진 리액트의 렌더링은 동기식으로 작동함.
따라서 렌더링 과정이 길어지면 애플리케이션의 성능 저하로 이어지고, 결과적으로 그 시간만큼 브라우저의 다른 작업을 지연시킴.
이는 렌더링 프로세스의 특징을 본다면 당연한데 비동기 방식으로 이루어지면 하나의 상태에 대해 여러가지 다른 UI가 노출될 것 이다.
a 를 클릭 b → b1, c → c1, a를 클릭 b, c → c1
 
그럼에도 이런 비동기 렌더링 시나리오가 유효한 경우가 있는데 B 컴포넌트의 렌더링 작업이 무거워 빠르게 렌더링 할 수 있는 C라도 먼저 보여주는 경우가 된다.
이런 동시성 렌더링은 React 18에 도입되어 렌더링 중 렌더 단계가 비동기로 작동해 특정 렌더링의 우선순위를 낮추거나, 중단하거나, 경우에 따라서는 포기할 수 있다.
이를 통해 브라우저의 동기 작업을 차단하지 않고 백그라운드에서 새로운 리액트 트리를 준비할 수 있다.