[2팀 이정민] Chapter 2-2. 나만의 React 만들기#57
Open
LEE-jm96 wants to merge 8 commits intohanghae-plus:easyfrom
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
과제 체크포인트
배포 링크
https://lee-jm96.github.io/front_7th_chapter2-2/
기본과제
가상돔을 기반으로 렌더링하기
이벤트 위임
심화 과제
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 렌더링 원리를 단계별로 이해할 수 있었습니다.
먼저, JSX는 브라우저가 직접 이해할 수 있는 문법이 아니기 때문에 Babel에 의해 함수 호출 형태로 변환됩니다. React에서는 createElement, 이번 과제에서는 createVNode가 그 역할을 수행합니다.
→ Babel →
createVNode("div", { id: "app" }, "Hello");이 함수는 실제 DOM과는 독립된, 다음과 같은 구조의 가상 노드(Virtual Node)를 생성합니다.
{ type: "div", props: { id: "app" }, children: ["Hello"] }VNode를 생성할 때 children이 다음과 같이 다양할 수 있습니다.
문자열 + VNode 섞임
null, undefined, boolean 값
배열에 중첩된 배열(children 안의 children)
조건부 렌더링 결과로 생기는 의도치 않은 값들
노멀라이즈 단계는 이런 children을 정리하여 일관된 구조로 만드는 역할을 합니다.
주요 작업은 다음과 같습니다.
중첩 배열을 평탄화(flat)하여 1차 배열로 통일
null, false, true 같은 값을 제거
텍스트는 텍스트 노드로 유지
children을 모두 표준화된 형태로 변환
이 작업은 각 부모의 children 배열 내부에서만 이루어지기 때문에 depth(계층 구조)는 유지됩니다.
정규화된 VNode는 이후 실제 DOM 노드로 변환됩니다.
이 작업은 브라우저 API인 document.createElement, appendChild, setAttribute 등을 통해 구현됩니다.
즉, VNode의 type으로 HTML 요소 생성
props를 DOM 속성으로 등록
children을 재귀적으로 반복하여 DOM 트리를 구성
이 단계에서 실제 브라우저가 인식하는 물리적인 DOM 구조가 형성됩니다.
React의 핵심은 전체를 다시 그리지 않고 변경된 부분만 업데이트하는 것입니다.
렌더링이 다시 일어나면 새로운 VNode가 생성되고, 기존 VNode와 비교(diff)하는 과정이 필요합니다.
diff 알고리즘은 다음을 비교하니다.
type이 달라졌는가? → DOM 노드를 통째로 교체
props가 변경되었는가? → 필요한 부분만 수정
children이 달라졌는가? → key, 순서, 개수 등을 비교하며 최소 수정 적용
이러한 비교는 VNode가 트리 구조를 유지하기 때문에 가능합니다.
즉, 노멀라이즈가 children을 flat하게 만들더라도 depth는 부모-자식 구조로 유지되므로 diff가 정확하게 동작합니다.
diff 알고리즘이 실제 DOM을 업데이트하면 브라우저 렌더링 엔진이 이를 반영하여 화면이 그려집니다.
이 과정은 브라우저 내부에서 일어나는:
Layout (레이아웃 계산)
Paint (픽셀 칠하기)
Composite (레이어 병합)
단계로 이어집니다.
React는 이 전체 과정을 효율적으로 만들어 불필요한 DOM 업데이트를 최소화하여 성능을 높입니다.
React는 각 DOM 요소에 이벤트 리스너를 직접 붙이지 않습니다.
대신 최상단(root)에서 단 하나의 이벤트 리스너만 등록합니다.
이를 이벤트 위임(Event Delegation)이라고 합니다.
이 방식을 쓰는 이유 ?
성능 최적화 (대형 앱에서 수천 개의 이벤트를 직접 추가하는 건 비효율)
이벤트 버블링을 활용하여 가장 가까운 VNode 인스턴스에 이벤트 전달
React 내부에서 합성 이벤트(Synthetic Event)로 처리하여 일관된 동작 보장
즉, 클릭이나 변화가 발생하면 이벤트는 root까지 버블링되고, React는 해당 DOM → 해당 VNode → 해당 컴포넌트로 연결시키는 방식으로 처리합니다.
과제 피드백
AI를 활용해 핵심 로직을 먼저 구현해보고, 이후에 직접 코드를 따라가며 이해하는 방식을 선택했습니다. 이 과정 덕분에 전체적인 렌더링 원리와 Virtual DOM의 장점을 큰 그림에서 이해할 수 있었고, 특히 createVNode와 normalize 단계까지는 로직의 흐름을 비교적 명확하게 파악할 수 있었습니다.
하지만 그 이후 단계로 갈수록 구현 난이도가 점점 높아졌고, 제가 이해해야 할 부분이 훨씬 많다는 걸 깨달았습니다. 그래서 이번 항해가 끝나더라도 이 내용을 꾸준히 공부해서 완전히 습득하고, 나아가 심화 과제까지 직접 구현해 보고 싶다는 동기부여가 생겼습니다.
리뷰 받고 싶은 내용
안녕하세요 코치님!
이번 주 과제와는 별개로 질문이 하나 있습니다!
타입스크립트 사전 스터디를 진행하면서 다양한 데이터 유형의 타입 지정 방법을 배우긴 했지만, 실무 코드에서 자주 보이는 RefObject나 AxiosInstance 같은 세부적인 타입들은 충분히 다루지 못했던 것 같습니다.
스터디에서는 주로 원시값, 객체, 함수, 그리고 함수 반환 타입 정도만 다뤘기 때문에, 이런 더 복잡한 타입들을 어떻게 공부하면 좋을지 궁금합니다!