Skip to content

Conversation

@taejun0
Copy link

@taejun0 taejun0 commented Nov 17, 2025

과제 체크포인트

배포 링크

https://taejun0.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 비교 기반 컴포넌트 메모이제이션

심화 과제 완료 기준: advanced.hooks.test.tsx, advanced.hoc.test.tsx

과제 셀프회고

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

Reconciliation의 anchor 메커니즘과 DOM 재배치 로직:
가장 어려웠던 부분은 key 기반 자식 노드 재배치였습니다. [a, b, c][b, c, a]로 변경할 때, b가 첫 번째 위치로 이동해야 하는데 계속 실패했습니다.

처음에는 단순히 nextSibling을 anchor로 사용했지만, 재배치 과정에서 DOM이 아직 이동하지 않은 상태이기 때문에 anchor 계산이 잘못되었습니다.

해결책은 역순으로 anchor를 계산하는 것이었습니다:

  1. nextChildren을 역순으로 순회하면서 각 자식의 anchor를 계산
  2. key가 있는 경우, 기존 위치의 DOM 노드를 먼저 찾아 anchor로 사용
  3. key로 찾지 못한 경우, 재배치 후 위치의 자식 DOM을 anchor로 사용
  4. insertBefore 호출 시, currentDom이 이미 anchor.previousSibling이 아니거나, 첫 번째 위치가 아니면 무조건 이동

특히 b가 첫 번째로 이동할 때, currentDom.nextSibling === anchor인 경우에도 currentPrevSibling !== null이면 이동해야 한다는 점을 깨달았습니다. 이는 DOM이 이미 올바른 상대 위치에 있어도, 절대 위치가 다르면 이동이 필요하기 때문입니다.

컴포넌트 경로 변경과 상태 마이그레이션:
Footer 컴포넌트의 상태가 Item 개수 변경 시 유지되지 않는 문제가 있었습니다. 원인은 컴포넌트의 경로(path)가 변경되면 hooks.statehooks.cursor가 새 경로에 없어서 상태가 초기화되는 것이었습니다.

처음에는 경로 계산 로직을 수정하려고 했지만, 실제 문제는 경로 변경 시 상태를 마이그레이션하지 않는 것이었습니다.

해결책은 updateComponent에서 경로가 변경될 때:

  1. oldPath에서 hooks.state, hooks.cursor, hooks.visited를 읽어옴
  2. newPath로 복사
  3. oldPath의 데이터를 삭제
  4. 이 작업을 context.hooks.cursor.set(path, 0) 호출 전에 수행

이렇게 하면 경로가 변경되어도 컴포넌트의 상태가 유지됩니다.

Fragment의 DOM 노드 수집과 제거:
{hasChildren && <>...</>}에서 hasChildren이 false가 되면 Fragment의 자식들이 제거되어야 하는데, 계속 남아있었습니다.

문제는 getDomNodes 함수가 FRAGMENTCOMPONENT를 제대로 처리하지 못했던 것입니다. instance.dom을 직접 반환하면 Fragment는 DOM이 없어서 빈 배열이 반환되었습니다.

해결책은 getDomNodes에서:

  1. FRAGMENTCOMPONENTinstance.dom이 없으므로 재귀적으로 자식들의 DOM을 수집
  2. NodeTypes.FRAGMENTNodeTypes.COMPONENT를 명시적으로 비교 (이전에는 instance.kind === 'FRAGMENT'로 비교했는데 타입 오류 발생)
  3. updateComponent에서 childNode가 null이거나 빈 children일 때, prevChild가 Fragment면 명시적으로 reconcile(parentDom, prevChild, null, path) 호출

이렇게 하면 Fragment의 모든 자식 DOM이 재귀적으로 제거됩니다.

기술적 성장

Reconciliation 알고리즘의 복잡성 이해:
이번 과제를 통해 Reconciliation이 단순히 "이전과 현재를 비교"하는 것이 아니라, 여러 전략을 조합해야 한다는 것을 깊이 이해했습니다:

  1. Key 기반 매칭: key가 있으면 key로 먼저 매칭 (최우선)
  2. Path 기반 매칭: key가 없으면 같은 경로의 인스턴스 재사용
  3. 타입 기반 매칭: 경로도 다르면 같은 타입의 인스턴스 재사용
  4. 위치 고려: 마지막 위치의 경우, 같은 타입의 마지막 자식을 우선 선택

또한 anchor를 사용한 DOM 재배치 로직에서:

  • DOM이 아직 이동하지 않은 상태에서 anchor를 계산해야 함
  • 역순 계산이 필요한 이유
  • insertBefore의 동작 원리와 previousSibling의 관계

디버깅 전략의 중요성:
문제가 계속 해결되지 않을 때, "같은 방식으로 계속 시도"하는 것보다는:

  1. 근본적인 재작성: reconciliation 로직을 처음부터 다시 작성
  2. 상세한 로깅: 모든 함수에 console.log를 추가해 실행 흐름 추적
  3. 단계별 검증: 각 단계에서 예상값과 실제값을 비교

특히 console.log를 전략적으로 배치해, 어떤 함수에서 문제가 발생하는지 정확히 파악한 것이 큰 도움이 되었습니다.

상태 관리와 경로의 관계:
React의 Hook 시스템이 컴포넌트 경로에 의존한다는 것을 직접 구현하며 이해했습니다:

  • 각 컴포넌트 인스턴스는 고유한 경로를 가짐
  • 경로가 변경되면 상태도 함께 마이그레이션해야 함
  • hooks.visited를 사용해 현재 렌더링에서 방문한 컴포넌트만 추적
  • cleanupUnusedHooks에서 방문하지 않은 컴포넌트의 상태를 정리

코드 품질

특히 만족스러운 구현:

  • anchor 계산 로직: 역순으로 계산하고, key 기반과 위치 기반을 조합한 로직이 정확하게 작동합니다.
  • 상태 마이그레이션: 경로 변경 시 상태를 자동으로 마이그레이션하는 로직이 깔끔합니다.
  • getDomNodes의 재귀적 처리: Fragment와 Component의 DOM을 재귀적으로 수집하는 로직이 명확합니다.

리팩토링이 필요한 부분:

  • reconcile 함수가 1000줄이 넘어 가독성이 떨어집니다. mountHost, updateHost, mountComponent, updateComponent 등을 별도 파일로 분리하면 좋을 것 같습니다.
  • updateHostupdateFragment의 로직이 거의 동일하므로, 공통 로직을 추출해 중복을 제거할 수 있습니다.
  • 디버깅용 console.log가 많이 남아있어 제거가 필요합니다.

코드 설계 관련 고민과 결정:

  • anchor 파라미터 추가: DOM 위치를 정확하게 제어하기 위해 reconcile 함수에 anchor 파라미터를 추가했습니다. 이는 재배치 로직을 크게 개선했지만, 함수 시그니처가 복잡해졌습니다.
  • 경로 기반 상태 관리: 문자열 경로("0.c0.i1.c2")를 사용해 계층 구조를 표현했습니다. 디버깅에 유용하지만, 경로 변경 시 마이그레이션이 필요해 복잡도가 증가했습니다.
  • 타입 비교 방식: NodeTypes.FRAGMENT와 같은 상수를 사용해 타입을 비교했습니다. 이는 타입 안정성을 높이지만, 런타임 비교가 필요합니다.

학습 효과 분석

가장 큰 배움이 있었던 부분:

  1. Reconciliation의 복잡성: 단순해 보이지만 실제로는 key 매칭, path 매칭, 타입 매칭, anchor 계산 등 여러 전략을 조합해야 합니다.
  2. DOM 조작의 정확성: insertBeforeremoveChild를 사용할 때, 노드의 현재 위치와 부모를 정확히 확인해야 합니다.
  3. 상태 마이그레이션의 필요성: 경로가 변경되면 상태도 함께 이동해야 한다는 점을 직접 구현하며 이해했습니다.

추가 학습이 필요한 영역:

  • React Fiber의 스케줄링: 현재는 마이크로태스크 큐를 사용했지만, React Fiber의 우선순위 기반 스케줄링을 학습하면 더 효율적일 것입니다.
  • 성능 최적화: 현재는 기본적인 최적화만 수행하므로, 메모이제이션 범위 확대, 가상화(virtualization) 등을 추가로 학습하면 좋을 것 같습니다.
  • 타입 시스템 개선: any 타입을 사용하는 부분을 더 엄격한 타입으로 개선할 수 있을 것 같습니다.

실무 적용 가능성:

  • Virtual DOM과 Reconciliation 개념은 React뿐만 아니라 Vue, Svelte 등 다른 프레임워크에서도 유사하게 사용되므로, 이번 학습이 프레임워크 이해에 도움이 될 것입니다.
  • Hook 시스템의 구현 원리를 이해하면, 커스텀 훅을 더 효과적으로 작성하고, 훅의 동작을 예측할 수 있을 것입니다.
  • 디버깅 전략(로깅, 단계별 검증)은 실무에서도 유용할 것입니다.

과제 피드백

과제에서 모호하거나 애매했던 부분:

  • createChildPath 함수의 siblings 파라미터: 실제로는 사용되지 않았는데, 시그니처에 포함되어 있어 혼란스러웠습니다.
  • Fragment의 children이 빈 배열일 때의 처리: {hasChildren && <>...</>}에서 hasChildren이 false일 때 Fragment가 null이 되어야 하는지, 빈 Fragment로 처리해야 하는지 명확하지 않았습니다. 테스트를 통해 null이 되어야 함을 확인했습니다.
  • anchor 계산의 정확한 시점: 재배치 과정에서 anchor를 언제 계산해야 하는지, DOM이 이동하기 전인지 후인지 명확하지 않았습니다.

과제에서 좋았던 부분:

  • 단계별 구현 가이드가 명확해서, 각 Phase를 순차적으로 진행할 수 있었습니다.
  • 테스트 코드가 상세해서, 각 기능의 동작을 정확하게 검증할 수 있었습니다. 특히 실패한 테스트의 오류 메시지가 명확해서 디버깅에 도움이 되었습니다.
  • Reconciliation의 복잡한 로직을 단계적으로 구현할 수 있도록 잘 구성되어 있었습니다.

리뷰 받고 싶은 내용

Reconciliation 로직의 구조 개선 방안:
현재 reconcile 함수가 1000줄이 넘어 가독성이 떨어집니다. mountHost, updateHost, mountComponent, updateComponent 등을 별도 파일로 분리하는 것이 좋을까요? 아니면 현재처럼 하나의 파일에 유지하는 것이 더 나을까요?

또한 updateHostupdateFragment의 로직이 거의 동일한데, 공통 로직을 추출해 중복을 제거할 수 있을까요? 어떤 방식으로 리팩토링하면 좋을지 조언해주실 수 있을까요?

컴포넌트 경로 관리 전략:
현재 컴포넌트 경로를 문자열("0.c0.i1.c2")로 표현하고 있는데, 경로 변경 시 상태 마이그레이션이 필요합니다. 더 효율적인 경로 관리 방식이 있을까요?

예를 들어:

  • 경로 객체를 사용해 타입 안정성을 높이는 방법
  • 경로 변경을 자동으로 감지해 마이그레이션하는 로직을 추가하는 방법
  • 경로 대신 다른 식별자(예: 고유 ID)를 사용하는 방법

어떤 방식이 더 나을지 조언해주실 수 있을까요?

anchor 계산 로직의 최적화:
현재 anchor 계산 로직이 복잡하고, 역순 순회를 사용하고 있습니다. 더 효율적이거나 명확한 방법이 있을까요?

특히 key 기반 재배치에서:

  • 기존 위치의 DOM을 찾는 로직
  • 재배치 후 위치의 DOM을 찾는 로직
  • insertBefore 호출 조건

이 부분들을 더 간결하게 표현할 수 있는 방법이 있을까요?

타입 안정성 개선:
현재 any 타입을 일부 사용하고 있는데, 더 엄격한 타입 정의로 개선할 수 있을까요?

특히:

  • normalizeNode 함수에서 any를 사용하는 부분
  • createElement의 children 타입
  • VNode의 type이 string | symbol | React.ComponentType인데, 이를 더 구체적으로 타입화할 수 있는 방법

이런 부분들을 어떻게 개선하면 좋을지 조언해주실 수 있을까요?

디버깅 전략:
문제가 계속 해결되지 않을 때, console.log를 추가해 디버깅하는 방식 외에 더 효과적인 방법이 있을까요?

예를 들어:

  • 브레이크포인트를 사용한 디버깅
  • 테스트 코드에 더 상세한 검증 로직 추가
  • 시각화 도구 사용

어떤 방식이 더 효율적일지 조언해주실 수 있을까요?

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