[5팀 김채영] Chapter2-2 나만의 React 만들기 - #30
Open
rlacodud wants to merge 12 commits into
Open
Conversation
- core/elements.ts: createElement, normalizeNode, createChildPath - utils/validators.ts: isEmptyValue - utils/equals.ts: shallowEquals, deepEquals
- core/types.ts: VNode/Instance/Context 타입 선언 - core/context.ts: 루트/훅 컨텍스트와 경로 스택 관리 - core/setup.ts: 컨테이너 초기화, 컨텍스트 리셋, 루트 렌더 트리거
- core/dom.ts: 속성/스타일/이벤트 적용 규칙, DOM 노드 탐색/삽입/제거
- utils/enqueue.ts: enqueue, withEnqueue로 마이크로태스크 큐 구성 - core/render.ts: render, enqueueRender로 루트 렌더 사이클 구현
- core/reconciler.ts: 마운트/업데이트/언마운트, 자식 비교, key/anchor 처리
- core/hooks.ts: 훅 상태 저장, useState, useEffect, cleanup/queue 관리
- hooks/useRef.ts: ref 객체 유지 - hooks/useMemo.ts, hooks/useCallback.ts: shallow 비교 기반 메모이제이션 - hooks/useDeepMemo.ts, hooks/useAutoCallback.ts: deep 비교/자동 콜백 헬퍼 - hocs/memo.ts, hocs/deepMemo.ts: props 비교 기반 컴포넌트 메모이제이션
rlacodud
commented
Nov 22, 2025
| import { createChildPath } from "./elements"; | ||
| import { isEmptyValue } from "../utils"; | ||
|
|
||
| function mount(parentDom: HTMLElement, node: VNode, path: string): Instance { |
Author
There was a problem hiding this comment.
전체적으로 해당 파일 작성하실 때 다들 어려움이 없으셨는지, 어떻게 코드를 작성했는지 궁금합니다.
저는 너무 어려웠거든요
There was a problem hiding this comment.
채영님도 fragment 타입을 만나면 dom에 생성하지 않도록 만드신거같은데
어차피 dom에 만들어질것도아닌데 인스턴스 객체를 만들지 않아도 되지않을까? 고민이 많았습니다.
fragment만나면 자식 노드들부터 탐색하면되는데 fragment자체에 인스턴스를 안만들어도 될거같은데 일관성을 위해 만들어야하는건가 아니면 인스턴스를 만들지 않는 방법도 있을까에 대해 고민을 좀 했습니다.
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://rlacodud.github.io/front_7th_chapter2-2
기본과제
Phase 1: VNode와 기초 유틸리티
core/elements.ts:createElement,normalizeNode,createChildPathutils/validators.ts:isEmptyValueutils/equals.ts:shallowEquals,deepEqualsPhase 2: 컨텍스트와 루트 초기화
core/types.ts: VNode/Instance/Context 타입 선언core/context.ts: 루트/훅 컨텍스트와 경로 스택 관리core/setup.ts: 컨테이너 초기화, 컨텍스트 리셋, 루트 렌더 트리거Phase 3: DOM 인터페이스 구축
core/dom.ts: 속성/스타일/이벤트 적용 규칙, DOM 노드 탐색/삽입/제거Phase 4: 렌더 스케줄링
utils/enqueue.ts:enqueue,withEnqueue로 마이크로태스크 큐 구성core/render.ts:render,enqueueRender로 루트 렌더 사이클 구현Phase 5: Reconciliation
core/reconciler.ts: 마운트/업데이트/언마운트, 자식 비교, key/anchor 처리core/dom.ts: Reconciliation에서 사용할 DOM 재배치 보조 함수 확인Phase 6: 기본 Hook 시스템
core/hooks.ts: 훅 상태 저장,useState,useEffect, cleanup/queue 관리core/context.ts: 훅 커서 증가, 방문 경로 기록, 미사용 훅 정리기본 과제 완료 기준:
basic.equals.test.tsx,basic.mini-react.test.tsx전부 통과심화과제
Phase 7: 확장 Hook & HOC
hooks/useRef.ts: ref 객체 유지hooks/useMemo.ts,hooks/useCallback.ts: shallow 비교 기반 메모이제이션hooks/useDeepMemo.ts,hooks/useAutoCallback.ts: deep 비교/자동 콜백 헬퍼hocs/memo.ts,hocs/deepMemo.ts: props 비교 기반 컴포넌트 메모이제이션과제 셀프회고
챕터2에 대한 회고
챕터1에 대한 회고를 작성한 게 엊그제같은데 벌써 챕터2에 대한 회고를 작성하고 있다니 시간이 빠르다는 걸 체감하게 됩니다.
챕터2는 건강과의 사투였던 것 같습니다.
하필 챕터2를 시작하면서 눈과 전체적인 체력에 이슈가 생겨서 챕터1 때와 비교했을 때 상대적으로 시간과 체력을 덜 투자하게 되어 아쉬움이 많습니다.
특히 제가 좋아하는 javascript를 활용하는 주차였기에 더욱 아쉬움이 큰 것 같습니다.
그럼에도 아하 모먼트가 많았던, 짜릿한 챕터였기도 한 것 같습니다.
챕터1 테스트코드는 초면이었던 상대를 알아가는 과정이었다면,
챕터2는 이미 알고 있던, 친숙한 친구에 대해 제대로 이해하고 알아가는 과정이었던 것 같습니다.
javascript의 이벤트 위임, react의 가상돔, 재조정 과정 등 단순한 정의만으로 알고 있던 개념들을 과제를 진행하며 체화할 수 있었습니다!
아하! 모먼트 (A-ha! Moment)
기술적 성장
🤔 가상돔(Virtual DOM)과 Diff 알고리즘, 그리고 React의 내부 동작 흐름
“React는 어떻게 이전 DOM과 현재 DOM의 차이를 알아내고,
필요한 부분만 최소한으로 업데이트할까?”
이 과제를 시작하기 전까지는 막연히
가상돔이 있다는 건 알고 있지만, 실제론 어떻게 구현된 걸까?라는 의문이 있었습니다.
과제를 통해 직접 React를 만들면서 React 구조의 원리를 코드 단위로 체감할 수 있었고 아래는 구현 과정에서 가장 핵심적인 흐름들에 대해 정리한 내용입니다.
1. 가상돔(Virtual DOM)이란?
브라우저의 실제 DOM은 조작하는 데 비용이 큰 구조이다.
이 때문에 React는 바로 DOM을 조작하지 않고,
우선 메모리 상에 DOM의 **추상화된 형태(VDOM)**를 만든 뒤, 변경점을 계산하여 필요한 부분만 실제 DOM에 반영한다.
React의 기본 렌더 과정
=> 이 전체 과정이 실제로는 아주 빠르게 일어나며,
=> React는 이 과정을 통해 DOM 조작 비용을 획기적으로 줄일 수 있다.
2. Virtual DOM은 어떻게 생겼을까?
다음 HTML이 있다고 하자
React는 이를 JS 객체 형태의 VNode로 표현한다:
이처럼 DOM을 순수 JS 객체로 추상화함으로써
브라우저 환경에 종속되지 않고 비교 로직을 효율적으로 수행할 수 있게 된다.
3. JSX와 createElement()
그러나 위와 같이 객체로 표현됐을 때 문제점은 가독성이다.
우리가 그동안 봐왔던 HTML 구조를 표현하는 방식과 다르기에 어떤 depth로, 어떤 방식으로 구성되어있는지 한눈에 파악하기 어렵다.
이를 해결하기 위해 JSX라는 것이 등장한다.
위 코드는 컴파일되면 다음과 같은 createElement() 호출로 변환된다:
그리고 이 createElement가 Virtual DOM 노드를 생성한다.
4. Diff 알고리즘(Reconciliation)의 핵심 규칙
React의 Diff 알고리즘은 트리를 전체 비교하지 않는다.
대신 몇 가지 단순하지만 강력한 규칙을 사용한다!
4-1) 타입이 같으면 같은 노드로 본다
=> DOM은 유지, props만 업데이트됨.
4-2) 타입이 다르면 완전히 다른 노드로 본다
→ 기존 DOM 제거 후 새로운 DOM 생성.
4-3) 리스트는 key로 비교한다
이 규칙이 가장 중요하다.
key가 없으면?
리스트를 단순히 앞에서부터 순서대로 비교한다.
즉, 전부 새로 만든다 => 매우 비효율적
key가 있으면?
React는 key를 기준으로 매칭한다:
=> DOM 재사용 + 위치만 재배치(최적화)
5. Diff 알고리즘 전체 흐름 정리
React의 Reconciliation은 다음 단계로 이루어진다.
1️⃣ 루트 비교
2️⃣ 자식 비교
3️⃣ 단일 노드 비교
4️⃣ 리스트 처리
=> 재사용 가능한 인스턴스는 그대로 두고
=> 순서만 조정하거나 제거/추가 결정
6. React 전체 렌더링 사이클의 흐름
React 내부에서는 렌더링이 아래 순서로 진행된다:
1️⃣ JSX → createElement()
=> Virtual DOM 생성
2️⃣ Virtual DOM → Reconciliation
=> 이전 VDOM과 비교하여 변경점 계산
3️⃣ DOM Patch
=> 필요한 부분만 실제 DOM에 반영
4️⃣ Hooks 관리
5️⃣ Effect Flush
렌더가 끝나면 effect queue를 비우고 이펙트와 cleanup을 실행한다.
코드 품질
만족스러운 부분
리팩토링하고 싶은 부분
학습 효과 분석
✅ 테스트코드를 통한 코드 역추론 경험
가상돔을 만드는 데 가장 핵심적인
core/elements.ts파일의createElement함수를 구현할 때 테스트코드를 통한 코드 역추론의 경험을 했습니다.제공되었던 기본 코드 구조를 확인해보면 type, originProps, rawChildren을 매개변수로 받고 각 매개변수의 type이 명시되어있습니다.
이 함수는 VNode 객체로 변환하는 역할을 한다고 했고 테스트코드를 확인해보면 해당 함수의 결과가 어떤 방식으로 return되어야 하는지 확인할 수 있습니다.
아래가 가장 기본적인 return 형태의 구조로 보이고 이제 아래의 함수 활용방식과 expect값을 통해 어떤 방식으로 처리해줘야 할지 추론할 수 있습니다.
해당값의 type인 TEXT_ELEMENT로 type이 정의되고 key는 역시나 null값, 그 내부에 보내줬던 string값 "Hello"가 nodeValue로 들어가는 걸 확인할 수 있다.
위와 같이 추론한 내용을 바탕으로 아래와 같이 코드를 작성할 수 있습니다.
type이나 originProps에 대한 부분은 간단히 처리할 수 있지만 중요한 부분은 rawChildren을 처리하는 부분입니다.
rawChildren은 세번째 인자부터 몇개가 넘어오든 처리해야 하는 값이므로 우선 배열의 평탄화가 필요하고
flat으로 평탄화 처리를 한 뒤 배열을 순회하며 각 요소를 VNode 형식으로 정규화합니다.
이 때 Fragment에 대한 처리가 필요한데, 그 외의 노드는 그대로 push해주면 되지만 Fragment는 wrapper 없이 children만 전달하는 컨테이너이기에 children을 넘겨줘야 하기 때문입니다.
과제 피드백
좋았던 점
어려웠던 점
리뷰 받고 싶은 내용
자세한 구현 과정과 회고는 아래 블로그에 정리했습니다! 😊
WIL 5주차_Chapter2-2. 나만의 React 만들기
5주차_Vanilla JS로 나만의 React 만들기