Skip to content

Conversation

@kimzeze
Copy link

@kimzeze kimzeze commented Nov 18, 2025

과제 체크포인트

배포 링크

https://kimzeze.github.io/front_7th_chapter2-2/

기본과제

Phase 1: VNode와 기초 유틸리티

  • core/elements.ts: createElement, normalizeNode, createChildPath
  • utils/validators.ts: isEmptyValue
  • utils/equals.ts: shallowEquals, deepEquals

Phase 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 비교 기반 컴포넌트 메모이제이션

과제 셀프회고

과제를 받았을 때 여전히 막막했습니다. README를 읽어도 reconciliation이 뭔지, VNode가 정확히 어떤 역할을 하는건지 감이 잘 안 왔습니다. 그래서 일단 AI AGENT를 통해 방향성을 잡고, 정말 단계별로 가이드를 주는대로 따라가면서, 해당 함수를 왜 작성해야되는지, 어떤 생각을 해야되는지, 예시는 뭐가 있는지에 대해서 최대한 문서를 먼저 만들어두고, 해당 스텝들을 따라가는 형태로 진행하였습니다.

초반에는 shallowEquals, deepEquals 같은 유틸리티 함수들이 왜 필요한지 이해가 안 갔는데, AI에게 "왜 Object.is만으로는 안돼?"라고 질문하면서 객체 비교의 깊이에 대해 배울 수 있었습니다. 특히 배열도 객체라는 사실(typeof [] === 'object')을 처음 js공부할 때 알았던 내용인데 다시 한번 상기할 수 있었습니다.

DOM 부분에서는 Property와 Attribute의 차이를 몰라서 한참 헤맸습니다. input에 value를 설정했는데 화면에 안 나타나서 "왜 안되지?"하고 AI에게 물어봤더니 setAttribute가 아니라 dom.value =로 직접 설정해야 한다는 걸 알게 됐습니다.

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

Hook이 배열에 저장되는 이유

useState를 구현할 때 왜 Map<path, 배열>로 저장하는지 이해가 안 갔습니다. AI가 설명해준 내용:

  • React는 변수명을 모른다
  • 오직 "몇 번째로 호출됐는지"만 기억한다
  • 그래서 배열 인덱스(cursor)로 접근한다

이걸 듣고 "그래서 조건문 안에 Hook 쓰면 안되는구나!"하고 조금이나마.. 원리에 대해서 이해했습니다.

enqueue는 왜 필요한가, 그냥 render() 호출하면 안되는지"

처음에는 setState할 때마다 바로 render()를 호출하면 되지 않나 생각했어요. AI가 예시를 들어주면서:

setCount(1); // render() 호출
setCount(2); // render() 호출
setCount(3); // render() 호출
// → 3번 렌더링! 비효율적!

이렇게 설명해주니 "아, 배치로 모아서 한 번만 렌더링하는거구나!" 이해가 됐습니다.
queueMicrotask도 처음 들어봤는데, 정말 모르는 개념이었는데 해당 관련된 부분을 실행 순서에 공부한게 좋았습니다.

3가지 큐:

1. Call Stack (동기 코드)
   ↓ 즉시 실행

2. Microtask Queue 
   ↓ 동기 끝나면 바로 실행

3. Task Queue (setTimeout 등)
   ↓ 마이크로태스크 끝나면 실행

기술적 성장

1. useState가 undefined를 반환하는 문제

테스트를 돌렸는데 useMemo에서 Cannot read properties of undefined 에러가 났습니다.
몇 시간 동안 코드를 뒤져도 원인을 못 찾았는데, 일단 AI를 통해서 디버깅하면서 해결하긴 했습니다 ㅠㅠ

  1. useRef가 undefined 반환
  2. useRef 안에서 useState 호출
  3. useState의 hookList[cursor]에 useEffect 데이터가 들어있음!

원인: useState 초기화 조건이 hookList[cursor] === undefined만 체크해서, 다른 타입의 Hook 데이터가 있으면 초기화를 안함.

해결: !("value" in hookList[cursor]) 조건 추가

이게 해결을 잘한건지는 모르겠는데, 화면에 나오는 것 자체가 급급하여 그대로 적용하였습니다....

2. 상품 데이터가 로드되지 않는 문제

앱을 실행하면 상품이 하나도 안 나오고 로딩만 나오는 좌절이 있었습니다. Network 탭을 봐도 API 요청 자체가 안 가고 있었습니다.

이 또한 시간이 없어... AI한테 전적으로 디버깅하면서 찾았습니다.

  1. "상품 데이터가 안 나와요" 문제 상항 제시...
  2. 스크린샷 찍어서 보여주기
  3. 중간중간 로그와 현재 상황을 스크린샷과 네트워크탭으로 AI에게 공유

AI가 한 것:

  1. useEffect에 로그 추가
  2. path와 cursor 확인
  3. 모든 컴포넌트가 같은 path 사용하고 있다는 걸 발견
  4. mountComponent에서 자식에게 새로운 path를 안 만들고 있었다는 걸 찾음
  5. createChildPath 추가해서 해결

학습한 점: 버그를 찾을 때 로그를 어디에 찍어야 하는지가 정말 중요하다는 것. 그리고 시스템적으로 생각하는 게 중요하다는 것.

코드 품질

학습 효과 분석

과제 피드백

리뷰 받고 싶은 내용

Phase 5 Reconciliation부터는 거의 정신을 놓을 뻔 했습니다.

Phase 1~4까지는 그래도 따라갈 수 있었는데 Phase 5에서 갑자기 난이도가 확 올라갔습니다.....

  • Key 기반 매칭 알고리즘
  • Hook state migration
  • Path 충돌 문제
  • Two-pass 순회

Phase 5에서 막혔을 때, 어떻게 접근하는 게 좋았을까요?

Phase 1~4를 더 꼼꼼하게 복습하면서 "왜 path가 필요한지", "컨텍스트가 어떻게 관리되는지" 조금 더 깊이 이해한 후 Phase 5로 가는것이 좋았을 지... React의 Reconciliation 개념을 공식문서를 통해 먼저 공부하고 구현하는게 좋았을지 ㅠㅠ 그냥 일단 하다보니, AI 의존도가 더 높아지고, 변경되는 코드를 머리가 따라가기 힘들었습니다..

kimzeze and others added 30 commits November 17, 2025 17:46
- TypeScript 타입 안전성 규칙
- 함수 30줄 제한
- Early return 패턴
- Reconciliation 4가지 케이스
- Hook 규칙
- Import 순서 가이드
- Type 8가지 정의
- Scope 7가지 정의
- 한글 커밋 메시지 사용
- Phase별 커밋 패턴 예시
- 커밋 전 회고 파일 작성 규칙
- review/ 폴더 사용법
- 한글 파일명 및 순서 규칙
- PR 작성 시 활용 방법
- 상세 예시 및 템플릿 제공
- .gitignore에 review/ 폴더 추가
- review/README.md 생성 (사용법 안내)
- 개인 회고 파일은 커밋하지 않도록 설정
- Phase 1-7 전체 체크리스트
Reconciliation의 핵심 함수들 구현:
- update: 타입별(TEXT, Fragment, Component, DOM) 업데이트 처리
- unmount: cleanup, 자식 정리, DOM 제거, Hook 정리
- reconcileChildren: 인덱스 기반 자식 배열 비교
- mountComponent: Hook 컨텍스트 설정 후 컴포넌트 함수 실행
- updateComponent: 기존 path 유지하며 컴포넌트 재실행
테스트 실행 중 발견한 3가지 버그 수정:

1. createElement - children 빈 배열 문제
   - children이 없을 때 props에 children: [] 포함되던 문제 수정
   - children.length > 0일 때만 children을 props에 포함

2. createElement - key 타입 변환 문제
   - 숫자 key가 문자열로 변환되던 문제 수정 (1 → "1")
   - key ?? null로 변경하여 원본 타입 유지

3. setup - instance 초기화 문제 (가장 심각)
   - setup이 매번 instance를 null로 초기화하던 문제 수정
   - reconcile이 항상 mount만 실행하고 update 실행 안 되던 문제
   - instance를 유지하고 isFirstRender로 첫 렌더링/재렌더링 구분
   - DOM 재사용 및 updateDomProps 정상 작동 가능

테스트 결과: 18개 → 29개 통과 (+11개 ✨)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Hook 시스템의 핵심 기능 구현:

**useState:**
- 컴포넌트별 상태 저장 (경로 + 커서 기반)
- 함수형 initializer 지원
- setState 함수 생성 (직접 값 & 함수 지원)
- Object.is로 값 비교 후 변경 시에만 재렌더링
- 커서 자동 증가로 Hook 순서 추적

**useEffect:**
- deps 배열 얕은 비교로 실행 여부 판단
- 이전 cleanup 함수 먼저 실행
- effect를 렌더링 후 비동기 실행을 위해 queue에 추가
- cleanup 함수 저장 및 재실행/unmount 시 호출
- deps 없으면 매번 실행, 빈 배열이면 첫 렌더링만

테스트 결과: 29개 → 33개 통과 (+4개)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
useEffect Hook에 kind 필드 추가:
- HookTypes.EFFECT로 Hook 타입 명시
- 과제 초기 설정 의도대로 타입 시스템 활용
- 코드 가독성 및 타입 안전성 향상

테스트 결과: 33개 통과 유지

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
새 container로 setup 호출 시 이전 DOM이 남아있던 문제 해결:

**문제:**
- 전역 instance로만 첫 렌더링 판단
- 다른 container로 setup 호출 시 container.innerHTML = "" 실행 안 됨
- 이전 테스트 DOM이 남아서 테스트 실패

**해결:**
- isNewContainer 체크 추가 (context.root.container !== container)
- 새 container면 container 비우고 instance 초기화
- 테스트 독립성 보장

**변경사항:**
1. isNewContainer = context.root.container !== container 추가
2. if (isFirstRender || isNewContainer) 조건 수정
3. 새 container일 때 instance = null 초기화

테스트 독립성 확보로 useState/useEffect 테스트 통과 예상

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
setup 함수에서 rootNode가 null일 때 명확한 에러를 던지도록 개선.
Fail Fast 원칙에 따라 잘못된 입력을 즉시 감지.

- rootNode null 체크 추가
- 입력 검증 완성 (container + rootNode)
- 테스트 통과: 51개 → 52개 (+1)
kimzeze and others added 24 commits November 21, 2025 02:59
flushEffects를 queueMicrotask로 감싸서 effect가 렌더링 이후
비동기적으로 실행되도록 개선. React의 실제 동작과 일치.

- flushEffects를 queueMicrotask로 감쌈
- 렌더링과 effect 실행 분리
- 테스트 통과: 52개 → 53개 (+1)
HTML boolean 속성을 올바르게 처리하도록 수정.
존재 여부만으로 true/false가 결정되는 HTML 표준을 준수.

- BOOLEAN_PROPS 상수 추가
- setDomProps에서 boolean 속성 특별 처리
- updateDomProps에서 boolean 속성 특별 처리
- 테스트 통과: 53개 → 54개 (+1)
unmount에서 useEffect cleanup이 호출되지 않던 문제 수정.
hookList의 모든 cleanup을 effect queue에 추가하여 비동기 실행.
중복 실행 방지를 위해 cleanup = undefined 설정.

- hookList 순회하여 모든 cleanup을 queue에 추가
- 비동기 실행으로 일관성 유지
- 중복 실행 방지
- 테스트 통과: 54개 → 56개 (+2)
# 주요 변경사항

## 1. key 기반 reconciliation
- reconcileChildren에서 key를 사용한 자식 매칭 구현
- oldKeyedInstances Map으로 key 기반 인스턴스 추적
- usedInstances Set으로 사용된 인스턴스 추적

## 2. DOM 순서 재배치
- key가 있는 자식의 DOM 노드 재배치 로직 추가
- lastDom을 추적하여 정확한 위치에 DOM 삽입
- insertBefore를 사용한 효율적인 DOM 조작

## 3. 타입 기반 매칭 (key 없는 경우)
- 인덱스 매칭 시 타입도 함께 확인
- 타입이 다르면 남은 oldChildren에서 같은 타입 검색
- 컴포넌트 재사용을 통한 상태 보존

## 4. Hook state migration
- updateComponent에서 path 변경 감지
- 변경된 path로 hook state 자동 이전
- 컴포넌트 위치 변경 시에도 상태 유지

## 테스트 결과
- 총 60개 테스트 중 59개 통과 (98.3%)
- key 기반 재배치 테스트 통과
- 중간 아이템 삭제 테스트 통과
- unmount cleanup 테스트 통과

## 남은 이슈
- 중첩된 컴포넌트에서 복잡한 상태 관리 시나리오 (1개 테스트)
  복수 번의 크기 변경 시 타입 기반 매칭의 한계

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
## 주요 변경사항

### 1. Key 기반 reconciliation 구현
- Two-pass 알고리즘 도입
  - First pass: key/타입 기반 매칭 및 migration 정보 수집
  - Second pass: 역순 migration → 정순 reconcile
- Key 기반 매칭으로 DOM 재사용 및 재배치
- 타입 기반 fallback으로 keyless 컴포넌트 state 보존
- DOM 재배치 로직 추가 (insertBefore + lastDom 추적)

### 2. Path collision 버그 수정
- Unmount 시 visited 체크 추가
  - migration된 컴포넌트의 hookList 보호
  - 현재 사용 중인 path는 삭제하지 않음
- Migration 시 oldChild.path 업데이트
  - hookList와 instance.path 동기화
  - updateComponent에서 중복 migration 방지

## 해결한 문제

### 문제 1: Key 없이 순서 변경 시 모든 DOM 재생성
- 원인: 인덱스 기반 매칭만 수행
- 해결: Key 기반 매칭으로 element identity 추적
- 결과: DOM 재사용으로 성능 향상

### 문제 2: 동적 리스트에서 Hook state 손실
- 원인: Path 충돌 시 migration된 hookList까지 삭제됨
- 해결: Visited 기반 보호 + oldChild.path 동기화
- 결과: Migration된 state 완벽 보존

## 기술적 세부사항

**reconcileChildren 알고리즘:**
1. oldChildren을 key/index별로 Map 생성
2. newChildren 순회하며 매칭 (key → index → type 순)
3. 역순 migration으로 path 충돌 방지
4. 정순 reconcile 수행
5. DOM 재배치
6. 미사용 instance unmount

**Path collision 해결:**
- Unmount 시: `!visited.has(path)` 체크 후 삭제
- Migration 시: `oldChild.path = childPath` 동기화

## 테스트 결과

- 56/60 → 60/60 테스트 통과
- Key 기반 재배치 ✅
- 타입 기반 state 보존 ✅
- 중첩 컴포넌트 useState ✅
- Fragment 동적 렌더링 ✅

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
HOC에서 useRef 사용 시 컴포넌트 경로 불안정 문제로 캐시가 유지되지 않는 버그 수정.
클로저 변수로 캐시를 저장하여 안정적인 메모이제이션 구현.
E2E 테스트에서 Missing ./jsx-runtime 에러 해결을 위해
package.json에 jsx-runtime export 추가 및 파일 생성
loadProductsAndCategories 시작 시 categories를 명시적으로 비워서
'카테고리 로딩 중...' 텍스트가 E2E 테스트에서 감지될 수 있도록 수정
categories 초기값을 {}에서 null로 변경하여
초기 렌더링 시 '카테고리 로딩 중...' 텍스트가 확실히 표시되도록 수정.
SearchBar에서 null 체크 추가.
E2E 테스트에서 '카테고리 로딩 중...' 상태를 감지할 수 있도록
MSW delay를 200ms에서 1000ms로 증가
- Remove separate jsx-runtime.ts file
- Export jsx, jsxs, Fragment directly from elements.ts
- Point jsx-runtime package export to elements.ts
- Add ComponentType export to types.ts for reconciler
- circular dependency 방지를 위해 ../core 대신 ../core/hooks에서 직접 import
- useRef 대신 useState를 직접 사용하여 circular dependency 방지
- 테스트는 여전히 모두 통과
- setState를 렌더링 중에 호출하는 문제 해결
- 테스트 12/12 통과
- Fix useState to reinitialize when encountering non-state hook data
- Fix useRef to import useState from ../core instead of ../core/hooks
- Add null check in setDomProps to handle undefined props
- Add optional chaining in reconciler for node.props.children
- Revert MSW and app configuration changes to original state
컴포넌트가 반환한 자식 VNode에 부모와 같은 path를 사용하여
여러 컴포넌트가 같은 hookList를 공유하는 문제를 수정합니다.

mountComponent와 updateComponent에서 createChildPath를 사용하여
자식에게 고유한 경로를 생성하도록 변경했습니다.

이로 인해 각 컴포넌트가 독립된 훅 상태를 유지하고,
useEffect가 정상적으로 실행됩니다.
GitHub Actions를 사용한 자동 배포 설정을 추가합니다.
- main 및 release-* 브랜치 push 시 자동 배포
- SPA fallback을 위한 404.html 생성
- packages/app/dist 폴더를 GitHub Pages로 배포
@joshuayeyo
Copy link

[Joshua] 4팀 코드리뷰

과제를 받았을 때 여전히 막막했습니다. README를 읽어도 reconciliation이 뭔지, VNode가 정확히 어떤 역할을 하는건지 감이 잘 안 왔습니다. 그래서 일단 AI AGENT를 통해 방향성을 잡고, 정말 단계별로 가이드를 주는대로 따라가면서, 해당 함수를 왜 작성해야되는지, 어떤 생각을 해야되는지, 예시는 뭐가 있는지에 대해서 최대한 문서를 먼저 만들어두고, 해당 스텝들을 따라가는 형태로 진행하였습니다.

이번 주 계속 바쁘신 것 같았는데 결국 마무리 하셨군요,, 역시 4팀 최고 멋쟁이,,👍🏻

저도 똑같았던 것 같습니다.

뭐 우뜨카라고.

하는 생각이 가장 먼저 들었던 주차였기에, 처음엔 AI를 통해 방향성을 잡았던 것 같아요.

상세 코드 리뷰는 집 도착해서 해볼게요~ 이번 주차도 고생 너어어무 많으셨습니다~!

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.

2 participants