Skip to content

[2팀 이정민] Chapter 2-2. 나만의 React 만들기#57

Open
LEE-jm96 wants to merge 8 commits intohanghae-plus:easyfrom
LEE-jm96:easy
Open

[2팀 이정민] Chapter 2-2. 나만의 React 만들기#57
LEE-jm96 wants to merge 8 commits intohanghae-plus:easyfrom
LEE-jm96:easy

Conversation

@LEE-jm96
Copy link

@LEE-jm96 LEE-jm96 commented Nov 20, 2025

과제 체크포인트

배포 링크

https://lee-jm96.github.io/front_7th_chapter2-2/

기본과제

가상돔을 기반으로 렌더링하기

  • createVNode 함수를 이용하여 vNode를 만든다.
  • normalizeVNode 함수를 이용하여 vNode를 정규화한다.
  • createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
  • 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.

이벤트 위임

  • 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
  • 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
  • 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다

심화 과제

Diff 알고리즘 구현

  • 초기 렌더링이 올바르게 수행되어야 한다
  • diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
  • 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
  • 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
  • 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다

과제 셀프회고

아하! 모먼트 (A-ha! Moment)

과제를 진행하면서 createVNode 로직을 구현할 때 한 가지 의문이 생겼습니다.
children을 평탄화(flat)하면 계층 구조(depth)가 사라지는 것 아닌가?
만약 depth가 무너진다면, 이후 가상 DOM과 실제 DOM을 비교(diff)할 때 부모-자식 구조를 어떻게 다시 판단할 수 있을지 궁금했습니다.

처음에는 flat을 사용하면 모든 자식 노드가 하나의 레벨로 섞여 버려, 어떤 요소가 어떤 부모에 속해 있는지 구분할 수 없을 것처럼 보였습니다. 하지만 구현을 진행하고 실제 라이브러리들의 동작 방식을 살펴보면서, 이 부분이 어떻게 처리되는지 자연스럽게 이해하게 되었습니다.

결론적으로, 평탄화(flat)는 전체 트리를 평평하게 만드는 것이 아니라, 각 부모의 children 배열 내부에서만 적용되는 작업이라는 것을 알게 되었습니다. 즉, flat(Infinity)가 적용되는 범위는 한 부모가 가진 children 리스트 안에서만이며, 부모-자식 관계나 전체 트리 구조에는 영향을 주지 않는다는 점을 이해하게 되었습니다.

기술적 성장

실무에서는 개발자 도구로 바벨링된 코드를 확인하다 보면 createElement라는 함수를 자주 보게 됩니다. 그동안 저는 이것을 단순히 “엘리먼트를 만들어주는 함수” 정도로만 이해하고 넘어갔고, 내부 동작 방식까지 깊게 살펴보지는 않았습니다.

하지만 이번 과제를 진행하면서 createElement가 렌더링 과정에서 어떤 위치에 있고 어떤 역할을 수행하는지 보다 명확하게 이해할 수 있었습니다.

제가 진행한 과제에서는 createVNode가 React의 createElement와 동일한 역할을 수행했습니다. 이 함수는 JSX를 입력받아 { type, props, children } 형태의 가상 노드(Virtual Node)로 변환하는 단계이며, 실제 DOM과는 완전히 독립적인 렌더링 전 준비 과정, 즉 화면을 만들기 위한 “설계도”를 구성하는 역할을 담당합니다.

그 다음 단계에서는 제가 구현한 createElement 함수가 이러한 VNode를 기반으로 실제 브라우저 DOM 노드를 생성합니다. 이 과정에서 document.createElement, appendChild 같은 실제 DOM API가 사용되며, 가상 노드(설계도)가 실제 화면 요소로 변환됩니다.

마지막으로 이렇게 만들어진 DOM을 기존 DOM과 비교하여 변경된 부분만 업데이트하는 diff 과정까지 포함하면, 전체 렌더링 사이클이 완성됩니다. 즉, JSX → VNode 생성 → 실제 DOM 생성 → 변경된 부분만 갱신(diff)이라는 흐름으로 React가 동작한다는 것을 직접 구현해보며 더욱 명확하게 이해하게 되었습니다.

이번 과제를 통해 그동안 단순히 지나쳤던 createElement의 책임과 전체 렌더링 사이클의 구조를 제대로 파악하게 되었다는 점이 큰 배움이었습니다.

코드 품질

학습 효과 분석

React 렌더링 원리를 단계별로 이해할 수 있었습니다.

  1. createVNode: JSX → 가상 노드(VNode) 생성

먼저, JSX는 브라우저가 직접 이해할 수 있는 문법이 아니기 때문에 Babel에 의해 함수 호출 형태로 변환됩니다. React에서는 createElement, 이번 과제에서는 createVNode가 그 역할을 수행합니다.

Hello

→ Babel →
createVNode("div", { id: "app" }, "Hello");

이 함수는 실제 DOM과는 독립된, 다음과 같은 구조의 가상 노드(Virtual Node)를 생성합니다.
{ type: "div", props: { id: "app" }, children: ["Hello"] }

  1. 노멀라이즈(Normalize): children 정제 및 구조 통일

VNode를 생성할 때 children이 다음과 같이 다양할 수 있습니다.

문자열 + VNode 섞임

null, undefined, boolean 값

배열에 중첩된 배열(children 안의 children)

조건부 렌더링 결과로 생기는 의도치 않은 값들

노멀라이즈 단계는 이런 children을 정리하여 일관된 구조로 만드는 역할을 합니다.

주요 작업은 다음과 같습니다.

중첩 배열을 평탄화(flat)하여 1차 배열로 통일

null, false, true 같은 값을 제거

텍스트는 텍스트 노드로 유지

children을 모두 표준화된 형태로 변환

이 작업은 각 부모의 children 배열 내부에서만 이루어지기 때문에 depth(계층 구조)는 유지됩니다.

  1. createElement: VNode → 실제 DOM 생성

정규화된 VNode는 이후 실제 DOM 노드로 변환됩니다.
이 작업은 브라우저 API인 document.createElement, appendChild, setAttribute 등을 통해 구현됩니다.

즉, VNode의 type으로 HTML 요소 생성

props를 DOM 속성으로 등록

children을 재귀적으로 반복하여 DOM 트리를 구성

이 단계에서 실제 브라우저가 인식하는 물리적인 DOM 구조가 형성됩니다.

  1. 업데이트 단계: diff 알고리즘
    React의 핵심은 전체를 다시 그리지 않고 변경된 부분만 업데이트하는 것입니다.

렌더링이 다시 일어나면 새로운 VNode가 생성되고, 기존 VNode와 비교(diff)하는 과정이 필요합니다.

diff 알고리즘은 다음을 비교하니다.

type이 달라졌는가? → DOM 노드를 통째로 교체

props가 변경되었는가? → 필요한 부분만 수정

children이 달라졌는가? → key, 순서, 개수 등을 비교하며 최소 수정 적용

이러한 비교는 VNode가 트리 구조를 유지하기 때문에 가능합니다.
즉, 노멀라이즈가 children을 flat하게 만들더라도 depth는 부모-자식 구조로 유지되므로 diff가 정확하게 동작합니다.

  1. 렌더(Render): 실제 화면에 출력

diff 알고리즘이 실제 DOM을 업데이트하면 브라우저 렌더링 엔진이 이를 반영하여 화면이 그려집니다.

이 과정은 브라우저 내부에서 일어나는:

Layout (레이아웃 계산)

Paint (픽셀 칠하기)

Composite (레이어 병합)

단계로 이어집니다.

React는 이 전체 과정을 효율적으로 만들어 불필요한 DOM 업데이트를 최소화하여 성능을 높입니다.

  1. 이벤트 위임(Event Delegation): React의 이벤트 처리 방식

React는 각 DOM 요소에 이벤트 리스너를 직접 붙이지 않습니다.
대신 최상단(root)에서 단 하나의 이벤트 리스너만 등록합니다.
이를 이벤트 위임(Event Delegation)이라고 합니다.

이 방식을 쓰는 이유 ?

성능 최적화 (대형 앱에서 수천 개의 이벤트를 직접 추가하는 건 비효율)

이벤트 버블링을 활용하여 가장 가까운 VNode 인스턴스에 이벤트 전달

React 내부에서 합성 이벤트(Synthetic Event)로 처리하여 일관된 동작 보장

즉, 클릭이나 변화가 발생하면 이벤트는 root까지 버블링되고, React는 해당 DOM → 해당 VNode → 해당 컴포넌트로 연결시키는 방식으로 처리합니다.

과제 피드백

AI를 활용해 핵심 로직을 먼저 구현해보고, 이후에 직접 코드를 따라가며 이해하는 방식을 선택했습니다. 이 과정 덕분에 전체적인 렌더링 원리와 Virtual DOM의 장점을 큰 그림에서 이해할 수 있었고, 특히 createVNode와 normalize 단계까지는 로직의 흐름을 비교적 명확하게 파악할 수 있었습니다.

하지만 그 이후 단계로 갈수록 구현 난이도가 점점 높아졌고, 제가 이해해야 할 부분이 훨씬 많다는 걸 깨달았습니다. 그래서 이번 항해가 끝나더라도 이 내용을 꾸준히 공부해서 완전히 습득하고, 나아가 심화 과제까지 직접 구현해 보고 싶다는 동기부여가 생겼습니다.

리뷰 받고 싶은 내용

안녕하세요 코치님!
이번 주 과제와는 별개로 질문이 하나 있습니다!

타입스크립트 사전 스터디를 진행하면서 다양한 데이터 유형의 타입 지정 방법을 배우긴 했지만, 실무 코드에서 자주 보이는 RefObject나 AxiosInstance 같은 세부적인 타입들은 충분히 다루지 못했던 것 같습니다.
스터디에서는 주로 원시값, 객체, 함수, 그리고 함수 반환 타입 정도만 다뤘기 때문에, 이런 더 복잡한 타입들을 어떻게 공부하면 좋을지 궁금합니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant