조각조각 - 리액트 렌더링(브라우저 렌더링, 가상돔)

eocode·2024년 2월 21일
0

리액트 조각조각

목록 보기
11/11
post-thumbnail

리액트 렌더링?

리액트 렌더링 과정을 알아보기 전에 브라우저 렌더링에 대한 이해가 필요합니다. 먼저 브라우저 렌더링이 어떻게 진행되는지 과정과 특징을 알아보고 리액트 렌더링에 대해 알아보겠습니다.

브라우저 렌더링

브라우저 렌더링을 간략하게 살펴보면 초기 렌더링 과정(DOM, CSSOM, Render 트리 생성, 레이아웃, 페인팅)과 리렌더링(리플로우, 리페인팅) 과정으로 진행됩니다.

💡 DOM Tree : HTML 문서의 각 요소(엘리먼트)를 노드로 표현합니다. 각각의 노드들은 트리 구조로 연결됩니다.

html 문서

<html>
  <head>
  </head>
  <body>
    <h1>...</h1>
    <ol>
      <li>...</li>
      <li>...</li>
      <li>...</li>
    </ol>
  </body>
<html />

노드로 표현된 DOM 트리

tree

브라우저 렌더링 과정

step1
  1. 파싱
    브라우저는 HTML, CSS, JavaScript 등 웹 페이지 리소스를 서버로부터 가져옵니다.
  2. DOM 트리 생성
    HTML 코드로 DOM 트리가 생성됩니다.
  3. CSSOM 트리 생성
    CSS 코드로 CSSOM 트리가 생성됩니다.
step2
  1. 렌더링 트리 생성
    DOM 트리와 CSSOM 트리가 결합된 렌더링 트리가 생성됩니다. 이때 렌더링 트리에는 페이지를 렌더링하는 데 필요한 노드만 포함됩니다.
step3
  1. 레이아웃
    렌더링 트리를 바탕으로 각 요소의 정확한 위치와 크기를 계산합니다. ( + 상대적인 측정값 -> 절대적인 픽셀 )
step4
  1. 페인팅
    레이아웃 단계에서 계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고 화면에 요소들을 그립니다.
step5
  1. 리플로우, 리페인팅
    돔이 조작될 때 렌더 트리와 레이아웃 과정(리플로우)을 다시 수행하고 화면에 그립니다(리페인팅).

빈번한 돔 조작시 성능 저하 발생

돔이 조작될 때 마다 리플로우, 리페이팅 과정이 반복적으로 진행됩니다. DOM이 한곳이라도 수정되면 DOM 트리, CSSOM 트리를 새로 생성하여 렌더 트리를 새롭게 만듭니다. 이후, 리플로우 과정과 리페인팅 과정을 거쳐 리렌더링이 완료됩니다. 레이아웃 과정에서 적지않은 자원을 소모하게됩니다. 따라서 돔 조작이 빈번히 발생하는 경우 성능 저하가 발생합니다. 그렇기 때문에 개발자가 직접 돔 조작을 최소화하도록 코드를 개선해야합니다. 아래는 돔 조작이 빈번히 발생하는 경우의 코드와 돔 조작을 최소화 하도록 수정한 코드입니다.

//돔 조작이 빈번히 발생하는 경우
function onclick = () => {
  const likeList = document.querySelector("#LikeList");
  for(let i=0;i<99;i++){
    likeList.innerHTML += <li>${i}<li>;
  }
}

아래 코드는 돔 조작을 최소화 하도록 수정한 코드입니다. 반복문 내부에서 반복적으로 이루어지던 돔 조작을 반복문 밖으로 빼내고 한번에 모두 처리되도록 수정하였습니다. 이 경우 돔 조작은 한번만 이뤄지지만 위 코드와 동일하게 동작해 성능 저하를 방지할 수 있습니다.

//돔 조작 횟수 최적화
function onclick = () => {
  let list ="";
  const likeList = document.querySelector("#LikeList");
  for(let i=0;i<99;i++){
    list += <li>${i}<li>;
  }
        
  likeList.innerHTML = list;
}

리액트 렌더링

리액트 렌더링 이점

위에서 성능 최적화를 위해 돔 조작 횟수를 최소화하는 코드를 살펴보았습니다. 간단한 코드라면 일일히 돔 조작이 최소화 되도록 수정 가능하겠지만 코드가 복잡한 경우 모든 부분을 최적화하고 관리하기란 쉽지 않습니다.

리액트는 렌더링 과정중에 자동으로 이 귀찮고 번거로운 최적화를 수행해줍니다. 리액트에서 이 과정을 효율적으로 처리하기 위해 여러 최적화 기법이 사용되는데 그 중 대표적인 것이 바로 가상돔 입니다. 가상돔 최적화 기법을 사용하면 수정된 엘리먼트만 찾아서 실제 돔 조작을 최소화 할 수 있습니다. 이는 리액트가 UI를 효율적으로 업데이트하고 애플리케이션의 반응성을 유지하는데 핵심적인 역할을 합니다.
💡 가상돔 최적화 기법은 아래에서 좀 더 자세하게 알아보겠습니다.

리액트 렌더링 과정

앞서 브라우저 렌더링 과정을 살펴보았습니다. 이제 리액트 렌더링 과정을 알아보겠습니다. 리액트 렌더링은 크게 렌더링 단계와 커밋 단계로 진행됩니다.

  1. 렌더링 단계(Render Phase)
    • 컴포넌트의 render 함수를 호출하여 현재의 props와 state에 기초한 새로운 리액트 엘리먼트를 생성합니다.
    • 조건이 충족되어 리렌더링이 발생한다면 재조정(Reconciliation)과정을 거칩니다.
  2. 커밋 단계(Commit Phase)
    • 결정된 변경 사항을 실제 DOM에 반영하는 단계입니다.

재조정 : 이전 가상돔과 이후 가상돔을 비교하여 실제 DOM에 반영해야 할 변경 사항을 결정합니다.

브라우저 렌더링과 비교해서 살펴보겠습니다. 리액트 렌더링은 브라우저 렌더링과 별개의 것이 아닙니다. 돔 조작이 빈번할때 성능 저하가 발생한다는 브라우저 렌더링의 단점을 해결해주는 최적화 기법이 추가된 렌더링 방식입니다.

브라우저 렌더링에서 돔조작이 발생하면 바로 새로운 돔트리가 생성되어 렌더 트리가 재생성됩니다. 이후, 리플로우, 리페인팅 과정이 진행되어 리렌더링 됩니다. 즉 돔조작이 발생되면 실제 돔트리가 직접적으로 수정됩니다. 하지만 리액트 렌더링에선 다릅니다.

리액트에서 상태변화로 돔조작이 발생하였을때 바로 실제 돔이 수정되지 않습니다. 변경 사항이 적용되어 가상돔 트리가 업데이트 되고 이전 가상돔과 비교 과정 진행됩니다. 비교 과정이 완료 된 후 어떤 엘리먼트가 수정되어야 할지 결정되며 이 결정이 실제 돔에 적용됩니다.

🚨🚨🚨 리액트에서 사용되는 문법인 JSX 코드는 컴포넌트 렌더링을 위해 사용됩니다. JSX를 사용하여 정의된 컴포넌트 구조가 가상 DOM의 엘리먼트로 변환됩니다. 따라서 직접 돔조작하는 형식이 아닌 가상돔을 조작하게됩니다. 그렇기 때문에 state 변경 후, 리렌더링 발생할때 가상돔 비교 후 실제 돔 조작이 이뤄질수있습니다.

리액트 가상돔

리액트 가상돔에 대해 좀 더 자세히 알아보겠습니다.

실제 DOM⭐️ 가상 DOM
실제 DOM은 웹 페이지의 구조를 표현하는 계층적인 트리 구조입니다.가상 DOM은 실제 DOM의 가벼운 복사본으로, 메모리 상에 존재합니다.
실제 DOM 조작은 브라우저가 화면을 그리는데 필요한 모든 정보를 포함하고 있어 작업이 무겁고 시간이 많이 소요됩니다.리액트와 같은 라이브러리에서 사용되며, 실제 DOM에 접근하여 조작하는 대신 자바스크립트 객체를 통해 DOM의 상태를 관리합니다.
동적인 웹 애플리케이션에서 빈번한 DOM 조작은 비효율적이며, 복잡한 SPA(Single Page Application)에서는 성능 문제를 야기할 수 있습니다.변경 사항이 발생하면, 가상 DOM은 변경 전과 후의 상태를 비교하여 실제 DOM에 반영해야 할 최소한의 변경 사항만을 파악합니다. 이 과정은 재조정(Reconciliation) 알고리즘을 통해 수행되며, 성능 향상을 이끌어냅니다.

정리하자면 가상 DOM을 사용하면 불필요한 실제 DOM의 조작을 줄이고 렌더링 성능을 개선할 수 있습니다. 즉 효율적인 UI 업데이트를 가능하게 하여 사용자 경험을 개선하고 애플리케이션의 반응 속도를 높입니다.

렌더 과정

일반 브라우저 렌더링 과정

초기 렌더링

  1. 파싱
  2. DOM 트리 생성
  3. CSSOM 트리 생성
  4. 렌더링 트리 생성
  5. 레이아웃
  6. 페인팅

리렌더링

  1. 돔조작 발생
  2. 실제 돔 트리 변경
  3. 새로운 렌더링 트리 생성
  4. 리플로우, 리페인팅

리액트 렌더링 과정

초기 렌더링

  1. 애플리케이션이 처음 로드될 때, ReactDOM.render() 함수가 호출.
  2. 컴포넌트의 렌더링 메소드를 실행
  3. 반환된 JSX를 가상 DOM 엘리먼트로 변환하여 가상 DOM 트리를 구성.
  4. 실제 DOM에 적용
  5. 렌더링 트리 생성
  6. 레이아웃
  7. 페인팅

💡 CSSOM 트리의 생성은 브라우저의 HTML과 CSS 파싱 과정에서 자동으로 이루어집니다. 따라서, 리액트 애플리케이션의 초기 로드 시 또는 스타일 변경이 있을 때 CSSOM 트리가 생성되거나 업데이트됩니다.

리렌더링(리렌더링 조건 충족)

  1. 트리거 단계: 컴포넌트의 상태나 속성에 변화가 생기면 리액트는 해당 컴포넌트가 렌더링될 필요가 있다는 신호를 받아옵니다.
  2. 렌더 단계: 변경 사항이 적용된 새로운 가상 DOM 트리 생성 후 이전 가상DOM과 비교합니다. 이후 변경이 필요한 곳만 수정된 새로운 렌더링 트리를 생성합니다.
  3. 커밋 단계: 새로운 렌더링 트리가 완성되면 변경 사항을 실제 DOM에 반영합니다. 이 과정을 리렌더링이라 할 수 있습니다. 이 때 함수형 컴포넌트 리렌더링 되는 것이므로

함수 메모리 최적화

🚨🚨🚨 2번 과정에서 함수 컴포넌트가 리렌더링되며 내부 정의된 모든 함수가 새롭게 생성됩니다. 즉 모든 함수가 새로운 참조값을 가지게 됩니다. 따라서 이 함수를 프롭스로 넘겨주는 경우 참조값이 변경되었기 때문에 하위 컴포넌트가 리렌더링 됩니다. useCallback으로 메모리제이션하면 이 문제를 해결할 수 있습니다.

함수 컴포넌트 내부 생성된 함수가 리렌더링마다 새롭게 생성되는 것을 방지하기 위해서 useCallback을 사용할 수 있습니다. 또한 React.memo나 PureComponent를 사용하여 렌더링을 최적화할 수 있습니다. 이러한 기법들은 리액트 애플리케이션의 성능을 향상시키는 데 중요한 역할을 합니다.

const currentFuncComponent = () => {
  const func = useCallback(()=>{
      //리렌더링이 필요하지 않은 함수 컴포넌트 내부 함수  
  }, []);

  return (
    <>
      <하위 컴포넌트 func={func} />
    <>);
}

위 코드에서 func함수가 useCallback으로 처리되지 않았다면 currentFuncComponent 컴포넌트가 리렌더링 될 때마다 재생성되며 새로운 참조값을 가지게 됩니다. 따라서 하위 컴포넌트가 넘겨받은 프롭인 func 함수의 참조값이 변경됩니다. 이는 하위 컴포넌트가 func 함수가 변경되었다고 인식하게하고 리렌더링을 일으킵니다.

코드 처럼 useCallback으로 처리된 경우 참조값이 변경되지 않습니다. 따라서 하위 컴포넌트가 넘겨 받은 func 프롭의 참조값이 변경되지 않아 리렌더링이 발생하지 않게 됩니다.

주요 정리

  • 브라우저 렌더링에서 실제돔 조작이 빈번 히 발생하면 성능이 저하됩니다.
  • 리액트 렌더링은 가상돔 최적화 기법이 적용되어 실제돔 조작이 최소됩니다.
  • 리액트 렌더링의 렌더 과정에서 컴포넌트 내 모든 함수가 재 생성됩니다. 불필요한 리렌더링을 줄이기 위해 useCallback 등의 최적화 처리가 필요합니다.
  • 리액트 리렌더링을 잘 이해하여 불필요한 리렌더링 발생이 일어나지 않도록 해야합니다.

참고자료

profile
프론트엔드 개발자

0개의 댓글