ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React 딥다이브
    프론트엔드 2025. 10. 15. 21:18

    리액트 딥다이브

    • 리액트에 관련해서 궁금한게 많아 리액트 딥다이브 책을 통해 학습을 하고자 한다. 

     

    JSX가 만들어지는 과정 

    • JSX는 @bable/plugin-transform-react-jsx 플러그인을 알아야 한다. 
    • 이 플러그인은 JSX 구문을 자바스크립트가 이해할 수 있게 변환해준다고 한다. 
    const ComponentA = <A required={true}>Header</A>;
    
    const ComponentB = <>Hello</>;
    
    const ComponentC = (
       <div>
        <span>hello world</span> 
       </div>
    );
    • 위와 같이 컴포넌트가 있다고 가정해보자. 
    • 위 컴포넌트를 @bable/plugin-transform-react-jsx는 아래와 같이 변환한다. 
    'use strict'
    
    var ComponentA = React.createElement(
      A, 
      {
        required: true,
      },
      'Header'
    )
    
    var ComponentA = React.createElement(React.Fragment, nulll, 'Hello world')
    
    var ComponentA = React.createElement(
      C, 
      null,
      React.createElement('span', null, 'hello world')
    )

     

    가상 DOM과 리액트 파이버 

    • 🧐 리액트의 특징으로는 가상 DOM을 운영한다는 것이다. 가상 DOM이 왜 만들어졌고, 실제 DOM과 무슨 차이가 있는지 알아보자 
    • DOM이란 무엇인지부터 살펴봐야 한다.
    DOM은
    웹페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지 정보를 담고 있다.

     

    DOM의 브라우저 렌더링 과정

    • 가상 DOM을 다루기 앞서 DOM이 어떻게 생성되는지 살펴보자 
    브라우저 렌더링 과정

    1. 브라우저가 사용자가 요청한 주소를 방문해 HTML 파일을 다운받는다 
    2. 브라우저 렌더링 엔진은 HTML을 파싱해 DOM  노드로 구성된 트리를 만든다. 
    3. 2번 과정에서 CSS 만나면 CSS 파일도 다운받는다. 
    4. CSS도 파싱해 CSS로 구성된 트리(CSSOM)을 만든다. 
    5. 브라우저는 2번에서 만든 DOM을 순회하는데, 모든 노드를 방문하는것은 아니고 눈에 보이는 노드만 방문한다. display: none 같이 사용자 화면에 보이지 않는 요소는 작업하지 않는다
    6. 5번에서 제외된, 노드를 대상으로 노드에 대한 CSSOM 정보를 찾고 여기서 발견한 CSS 스타일 정보를 노드에 적용시킴. 이 DOM 노드에 CSS 적용시키는 방법은 2가지가 있다. 
          - 레이아웃(layout, reflow) : 각 노드가 브라우저 화면의 어느 좌표에 정확이 나타냐아 하는지 계산하는 과정 이 레이아웃 과정을 거치면 반드시 페인팅도 거친다 
          - 페인팅 : 레이아웃 단계를 거친 노드에 색과 같은 실제 유효한 모습을 그리는 과정

     

    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="style.css" type="text/css" href="./style.css" />
        <title>Document</title>
      </head>
      <body>
        <div stye="width: 100%">
          <div id="=text" style="width: 50%">Hello World!</div>
        </div>
      </body>
    </html>
    • 만약 위 HTML 코드가 있다고 가정해보자 브라우저는 다음과 같은 순서로 브라우저 렌더링 과정을 거친다
    1. HTML을 다운로드 한다. 다운로드와 함께 HTML을 분석한다. 
    2. 스타일 시트가 포함된 link 태그를 발견해 style.css를 다운로드한다. 
    3. body 태그 하단의 width는 100%이므로 좌우 100%로 넓이를 잡는다
    4. 3번 하단의 div는 width 50%이므로 부모의 50%를 너비로 잡아야 하니 전체 영역의 50%를 너비로 잡는다 
    5. 2번에서 다운로드한 CSS에 id='text'와 같은 스타일 정보를 결합한다. 
    6. 화면에 HTML 정보를 그리기위한 모든 정보가 준비되어 이를 바탕으로 렌더링 수행한다.

     

    가상 DOM의 탄생 배경

    • 🧐위 HTML과 CSS는 정말 복작하고 많은 비용이 발생하는것 같다. 
    • 어느 특정한 요소의 노출여부, 사이즈가 변경되면 요소의 위치와 크기를 다시 계산해야하는데, 이 때 레이아웃(리플로우)가 일어난다. 레이아웃이 일어나니 연쇄적으로 리페인팅 또한 같이 발생해 더 많은 비용이 발생한다. 
    • 또한 페이지가 변경이 된다면? 이 경우는 다시 처음부터 HTML을 새로받아 위 작업을 다시 시작해야 한다. 

     

    가상 DOM

    • 위와 같이 다양한 이유로 가상 DOM을 만들었다. 가상 DOM은 브라우저의 DOM이 아니다.
    • 가상 DOM은 웹페이지가 표시해야할 DOM을 메모리에 저장하고 리액트가 준비가 변경 준비가 완료되었을 때 실제 브라우저 DOM에 반영한다. 
    • 이로인해 브라우저가 아닌 메모리에서 계산하는 과정을 한번 거치게 되면 여러번 발생했을 때 렌더링 과정을 최소화 할 수 있다.

     

    가상 DOM 아키텍쳐, 파이버

    • 🧐 가상 DOM을 리액트는 어떻게 처리할까??? 
    • 가상 DOM과 렌더링 과정을 최적화 시켜주는 것이 React Fiber이다. 

     

    리액트 파이버

    • 리액트 파이버는 리액트에서 관리하는 객체다. 
    • 파이버는 재조정자(reconciler)가 관리하는데, 이는 가상DOM과 실제 DOM을 비교해 변경 사항을 수집하며, 이 둘의 차이가 있다면 관련된 정보를 가지고 있는 파이버를 기준으로 화면에 렌더링 요청하는 역할을 한다. 
    파이버는 재조정자(reconciler) : 가상 DOM과 실제 DOM을 비교해 리액트에서 어떤 부분을 새롭게 렌더링 해야하는지 비교하는 작업 알고리즘
    • 리액트 파이버의 과정은 모두 비동기로 일어난다. 
    • 파이버는 우선 하나의 작업 단위로 구성되어 있다. 리액트는 작업 단위를 하나씩 처리하고 finishedWork()라는 작업으로 마무리한다. 
    • 그리고 이 작업을 커밋해 실제 브라우저 DOM에 가시적인 변경사항을 만들어 낸다. 이러한 단계는 아래의 두 단계로 나눌 수 있다
    렌더 단계 : 리액트가 사용자에게 노출되지 않는 모든 비동기 작업을 수행한다. 이 단계에서 파이버의 작업, 우선순위를 지정하거나 중지시키거나 버리는 등의 작업이 일어난다.

    커밋단계 : 커밋 단계는 DOM에 실제 변경 사항을 반영하기 위한 작업이다. commitWork()가 실행되고 이 과정은 동기식으로 일어나고 중단될수 없다 
    • 파이버와 리액트 요소가 비슷해보이지만, 리액트는 렌더링이 발생할 때마다 새롭게 생성되지만 파이버는 가급적이면 재사용된다는 것이다

    파이버 트리

    • 파이버 트리는 리액트 내부에서 두 개가 존재한다. 
    • 하나는 현재 모습을 담은 상태 다른 하나는 작업 중인 상태를 나타내는 workInProgress 트리다. 
    • 리액트 파이버 작업이 끝나면 리액트는 포인터만 변경해 workInProgress 트리를 현재 트리로 바꾼다. 
    • 이러한 기술을 더블 버퍼링이라고 한다. 
    더블 버퍼링 : 그래픽을 통해 화면에 표시되는 것을 그리기 위해 내부적인 처리가 필요한데 이 처리를 거치면 사용자에게 다 그리지 못하는 모습이 발생한다. (한번에 모든 작업을 마무리해 다 그릴수 없기 때문이다) 
    이 상황을 방지하기 위해 보이지 않는 곳에서 미리 다음으로 그려야할 그림을 그리고, 이것이 완성되면 현재 상태를 새로운 그림으로 바꾸는 기법을 의미한다. 
    • 리액트에서도 미쳐 다 그리지 못한 모습을 노출시키지 않기 위해 더블 버퍼링 기법을 사용하는데, 이러한 더블 버퍼링을 위해 두 개의 트리가 존재하며, 이 더블 버퍼링은 커밋 단계에서 수행된다. 
    • 그림으로 보면  현재 UI 렌러링을 위해 존재하는 current 트리를 기준으로 모든 작업이 시작된다.
    • 만약 업데이트 발생시 파이버는 새로 받은 데이터를 새로운 workInProgress 트리를 빌드한다. 이 workInProgress 트리를 빌드하는 작업 끝나면 이 트리를 다음 렌더링 때 사용한다. 
    • 그리고 workInProgress 트리가 UI를 최종적으로 렌더링되어 반영이 되면 current가 이 workInProgress로 변경된다. 

     

    파이버의 작업 순서

    • 파이버와 트리와 파이버의 작동 흐름
      • 리액트는 beginWork() 함수를 실행해 파이버 작업 수행, 더이상 자식이 없는 파이버를 만날때까지 트리 형식으로 시작
      • 1번 작업 끝나면 completeWork() 함수를 실행해 파이버 작업을 완료함
      • 형제가 있으면 형제로 넘어간다
      • 2,3번이 끝나면 return으로 돌아가 자신의 작업이 완료되었음을 알림
    <A>
     <B1>안녕하세요</B1>
     <B2>
       <C1>
        <D1 />
        <D2 />
       </C1>
     </B2>
     <B3 />
    </A>
      • A의 beginWork()가 수행됨
      • A는 자식이 있으므로 B1으로 이동 후 beginWork()를 수행
      • B1은 자식 없으므로 completeWork()가 수행되고. B2로 넘어감
      • B2의 beginWork()가 수행됨 자식이 있으므로 C1으로 이동
      • C1의 beginWorK()가 수행되고 자식 D1으로 이동
      • D1의 beginWork()가 수행됨
      • D1은 자식이 없으므로 completeWork()가 수행되고 형제인 D2로 이동
      • D2는 자식이 없으므로 completeWork()가 수행됨 
      • D2도 자식 없어서 위로 이동함 D1, C1, B2순으로 completeWork()를 호출
      • B2는 형제 노드 B3로 이동후 beginWork()를 수행
      • B3의 completeWork() 수행되면 상위로 올라감
      • A1의 completeWork()가 수행됨
      • 루트 노드 완성되는 순간, 최종적으로 commitWork()가 수행되고 이 변경사항을 비교해 업데이트가 필요한 변경사항이 DOM에 반영됨위 JSX 코드가 있다고 가정해보자 

    • 위 과정을 그림으로 보면 다음과 같다. 
    • 🧐 setState 등으로 업데이트 발생한다면??

    -> 리액트는 앞서 만든 current 트리가 존재하고, setState등으로 업데이트 요청을 받으면 workInProgress 트리를 다시 빌드함

    이 말은 즉, 파이버가 이미 존재하므로 새로 생성할 필요없이 기존 파이버에서 업데이트된 props를 받아 파이버 내부에서 처리한다는 말이다.

    🧐 자 다시 보자 
    setState로 인한 업데이트가 발생하면 파이버가 존재한다는 건 알겠는데, 뭐가 정확히 어떻게 저장이 되어있는지 찾아봤다. 
    FiberNode {
      type,          // 어떤 컴포넌트인지 (함수, div 등)
      pendingProps,  // 새로 들어온 props
      memoizedProps, // 이전 렌더에서 사용된 props
      alternate,     // 반대쪽 버퍼(current ↔ workInProgress)
      stateNode,     // 실제 DOM 또는 컴포넌트 인스턴스 참조
      child, sibling, return, // 트리 연결 포인터들
      memoizedState,  // 현재 렌더에서 사용 중인 state 값 (useState/useReducer 등)
      updateQueue,    // 업데이트 대기열 (setState, dispatch 등)
    }​

     


    1. setState(newValue) 업데이트 발생
    2. setState함수 호출 후 Fiber.updateQueue에 update 객체를 추가
    3. current 트리를 기반으로 -> workInProgress 트리를 다시 만듦
    4. beginWork() 함수 호출한다. 
    5. beginWork가 끝나면 completeWork() 함수가 호출됨
    6. 이제 commitWork()로 이 때 DOM 업데이트가 일어남 

    😮아래와 같은 순서로 일어난다고 볼 수 있다 !!. 이를 기반으로 알 수 있던 것이 파이버가 이미 존재해서 기존 업데이트 된 파이버를 처리한다는 것이 이해가 되었다 

     

    🧹 정리 

    SPA

    • 위에서 작성하지는 않았지만, 리액트는 기본적으로 SPA라는 말을 많이 들어봤을 것이다. 
    • 한마디로 한 페이지에서 모든 것을 다 처리한다는 의미이다. 이로 인해 업데이트시 필요한 부분만 Javascript로 교체를 했었다. 
    • 기존 MPA에서는 업데이트가 일어나면 서버에 HTML을 요청후 DOM을 전체 교체 했었지만 SPA는 HTML을 전체 로드 후 그 다음 업데이트가 필요한 부분만 JS로 업데이트 했다. 
    • 하지만 이 또한 초기에 문제가 있었다. 바로 DOM 조작시 브라우저가 reflow, refainting을 일으나기도 했기 때문에 문제가 생겼다. 
    • 이를 해결하기 위해 가상 DOM이 생겨났다. 

     

    가상 DOM

    • “가상 DOM은 메모리에 가상 DOM을 생성해 파이버라는 아키텍처로 가상 DOM과 실제 DOM의 비교 후 렌더링을 최적화시킨다.”

     

    파이버

    • 파이버는 가상 DOM을 처리하고 스케줄링하는 알고리즘이다. 
    • 파이버는 재조정라는 알고리즘을 통해 가상 DOM과 실제 DOM을 비교해 업데이트가 필요한 DOM을 찾아낸다. 
    • 파이버는 렌더단계와 커밋단계가 있다. 
    • 렌더단계는 비동기 처리 작업을 수행하고 파이버의 작업, 우선순위 등을 결정한다. 
    • 커밋단계는 DOM에 실제 변경사항을 적용시킨다

     

    파이버 트리

    • 파이버 트리는 현재를 나타내는 current 트리와 작업 진행 예정인 workInProgress 트리가 있다. 
    • 파이버의 작업 순서는 beginWork가 수행되고, 끝나면 completeWork()가 수행된다. 

     

    '프론트엔드' 카테고리의 다른 글

    React 딥다이브  (1) 2025.10.16
    React 딥다이브  (0) 2025.10.15
    Next Image 최적화와 SVG  (0) 2025.10.12
    불필요한 렌더링으로 인한 UI 버벅임 개선하기  (0) 2025.10.10
    자바스크립트 딥다이브 (비동기 처리)  (0) 2025.10.08
Designed by Tistory.