ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Virtual DOM부터 Memoization까지
    REACT 2025. 12. 29. 15:17

    오늘도 아무것도 안하진 않았다. ✌🏻

     


    기나긴 2주간의 방학이 끝나고 다시 수업이 재개되었다.. 그리고 오랜만의 디깅타임~!!!

    오늘은 가상돔과 메모이제이션으로 최적화할 수 있는 방법들을 두루 배웠다.

    역시나 면접 단골 질문으로 나오는 가상돔에 대해서 딥하게 알게 되었는데 파면 팔수록 뭐가 이렇게 많은지

    머리는 아프지만 하나씩 천천히 정리해보자

     

     

    가상돔(Virtual DOM)

    쉽게 말해 찐 DOM의 복사본이라 생각하면 쉽다. 메모리에 저장되어 있으며 찐이 아니기 때문에
    UI에 접근할 수 있는 API(getElementById, querySelector 같은 친구들)이 없다.

     

    유튜브 별코딩님이 잘 설명해주고 있어서 가져왔다. 한번 보는걸 츄라이~

    https://www.youtube.com/watch?v=gc-kXt0tjTM

     

     

    DOM 복습해보기

    가상돔을 이해하기 위해서는 DOM에 대한 동작 원리 이해가 필요한데,
    지난번 웹 브라우저 렌더링 과정에서 말했던 것처럼 DOM이란 건 HTML 태그들 같은 노드들로 구성된 아키텍처고
    그것이 트리 형태(DOM Tree)로 이루어져 있다.
    거기서 무언가 변경사항이 일어나면 브라우저는 레이아웃을 다시 계산(Reflow)하고 화면을 다시 그리는(Repaint)
    비용이 큰 작업, 즉 시간이 많이 걸리게 되어 성능상 문제가 될 수 있다.

     

    가상돔(Virtual DOM)의 동작원리

    그래서 이 문제를 해결하기 위해 리액트에서는 항상 2개의 가상 DOM을 가지고 있다가
    State가 변경되면 그 두 개의 돔을 비교(Diffing)해 변경된 부분만 재조정(Reconciliation)한다.
    이 과정들을 반영하는 것을 Batch Update라고 말한다.

    영단어가 많이 나와서 당황스러운데 여기서 중요한 개념은 Reconciliation이다.

     

    Reconciliation과 Fiber

    재조정(Reconciliation)과 렌더링의 차이

    헷갈릴 수도 있는데 둘은 전혀 다른 단계이다.

    재조정은 무엇을 변경할지 계산하는 과정으로 순수하게 JS 메모리 안에서 일어난다.

    그리고 재조정에서 찾아낸 변경사항을 실제 DOM에 업데이트하는 과정을 렌더링이라고 말한다.

     

    Fiber

    리액트에선 이 두 단계를 분리한 덕분에 재조정은 비동기로 처리할 수도 있고 렌더링은 계산된 결과를 모아 한 번에 처리할 수 있다.

    바로 이 Fiber라는 자료구조가 React 16 버전부터 생기게 되었다.

    간단하게 핵심 내용만 말하면 렌더링 작업을 작은 단위로 나누어 분할 처리하고

    작업들의 우선순위를 매겨 급한 것부터 처리해준다.(Scheduling)

     

    또한 Suspense(비동기 작업에 대체 UI를 보여주는 Fallback) 같은 최신 기능들이 모두 Fiber 덕분에 가능해졌다.

    Fiber는 자료구조면서 동시에 아키텍처이기도 하다. 리액트 컴포넌트들은 하나의 Fiber Node로 만들어지는데 실제로 이렇게 생겼다.

    {
      type: 'div',              // 컴포넌트 타입
      props: {...},             // props
      stateNode: DOMNode,       // 실제 DOM 참조
      
      // 트리 구조를 위한 포인터들
      child: Fiber,             // 첫 번째 자식
      sibling: Fiber,           // 다음 형제
      return: Fiber,            // 부모
      
      // 작업 관련
      alternate: Fiber,         // 이전/다음 버전의 Fiber
      effectTag: 'UPDATE',      // 어떤 작업을 해야 하는지
    }
    

     

     

    누군가 "가상돔은 어떻게 쓰는 건가요?"라고 물으면 뭐라고 대답할 거임?

    가상돔은 리액트의 성능 최적화를 위한 아키텍처라 생각하면 된다. 실제로 조작하거나 사용할 수 있는 존재가 아니다.

    그리고 Shadow DOM과 헷갈릴 수도 있는데 실제 DOM에 스타일과 구조를 만들어내는 숨겨진 친구들이라 생각하면 된다.

    video 태그를 쓰면 내가 만들지 않았는데도 재생 버튼과 컨트롤 버튼들이 생기는 것들이 그런 것들이다.

     

    그리고 개인적으로 Reflow/Repaint와 재조정(Reconciliation)도 헷갈렸는데 재조정이 더 높은 레벨의 개념으로

    잘 모르겠으면 그냥 뭘 변경할지 계산하는 과정이라고 외우는 게 속편할 것 같다 ^^,,

     

    그럼 가상돔은 왜 쓰는 건데? Memoization

     

    결국 다 최적화하기 위해서이다. 여기서 메모이제이션을 알아가면 좋은데

    이전 결과를 저장해놨다 같은 값이라면 재계산하지 말고 재사용을 하는 개념을 말한다.

    가상돔이 효율적일 순 있지만 굳이 불필요하게 재조정을 만들 필요는 없기 때문이다. 애초에 안 만드는 게 제일 좋지!

     

    대표적으로 React.memo / useMemo / useCallback 들이 있다.

     

    React.memo

    const ChildComponent = React.memo(({ name }) => {
      console.log('렌더링!');
      return <div>{name}</div>;
    });
    
    // 부모가 리렌더링되어도
    // name prop이 안 바뀌었으면 ChildComponent는 리렌더링 안 함
    // → Reconciliation 자체가 발생하지 않음!
    

    부모에서 받은 Props가 재조정이 필요 없다면 React.memo로 자식 컴포만 리렌더링하지 않게 만들 수 있다.

    주로 계산이 무겁거나, props가 잘 안 바뀌는 컴포넌트나 리스트의 아이템들(주로 li 태그일 듯?)에게 쓰면 좋다.

    useMemo

    const ExpensiveComponent = ({ items }) => {
      // items가 안 바뀌면 재계산 안 함
      const sortedItems = useMemo(() => {
        console.log('정렬 중...');
        return items.sort((a, b) => a - b);
      }, [items]);
      
      return <List data={sortedItems} />;
    };
    

    .memo와 헷갈릴 수도 있는데 이 친구는 부모 컴포넌트에게 적용시켜주면 좋다.

    주로 복잡한 계산, 필터링, 정렬 등의 계산값을 저장해주는데 잘 사용된다.

    사용 방법은 useEffect와 동일하게 변경할 값, 의존성 배열(dependency)이 들어간다.

    useCallback

    const ParentComponent = () => {
      const [count, setCount] = useState(0);
      
      // count가 안 바뀌면 함수 참조가 유지됨
      const handleClick = useCallback(() => {
        console.log('clicked!');
      }, []);
      
      // ChildComponent가 React.memo로 감싸져 있어도
      // handleClick이 매번 새로 만들어지면 리렌더링됨
      // useCallback으로 참조를 유지하면 리렌더링 방지!
      return <ChildComponent onClick={handleClick} />;
    };
    

    useMemo와 동일하게 문법은 동일하나 다른 점은 얘는 함수를 저장하는 데 용이하다.

    주로 React.memo로 감싼 자식에게 함수 전달을 할 때나 useEffect 의존성 배열에 함수를 넣을 때 사용한다.

     

    그러나 메모이제이션 자체에도 비용이 든다.

    상태 변경
      ↓
    React.memo → "이 컴포넌트는 리렌더링 필요 없어" 판단
      ↓ (필요한 것만)
    useMemo → "이 계산은 다시 안 해도 돼" 판단
      ↓
    useCallback → "이 함수는 같은 거야" 판단
      ↓
    Reconciliation (재조정)
      - Virtual DOM 비교
      ↓
    실제 DOM 업데이트
    

    과도한 최적화는 오히려 역효과이다. 정말 필요할 때만 사용해야 하며 간단 계산이라면 안 쓰는 게 최선이겠지??!!!

     

     

     

    참고자료

    https://ko.legacy.reactjs.org/docs/faq-internals.html

     

    Virtual DOM과 Internals – React

    A JavaScript library for building user interfaces

    ko.legacy.reactjs.org

     

    https://velog.io/@hamjw0122/FE-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%EA%B0%80%EC%83%81-DOM-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0

     

    리액트의 가상 DOM과 Fiber

    리액트의 가상 DOM과 Fiber에 대해 알아봅시다!

    velog.io

     

     

     

Designed by Tistory.