Skip to content

Conversation

@ahnsummer
Copy link

@ahnsummer ahnsummer commented Nov 17, 2025

과제 체크포인트

배포 링크

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

과제 셀프회고

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

1. 구조 설계의 중요성

이전 구현 (2-1): 200줄짜리 render 함수 하나에 모든 로직이 섞여있었습니다. DOM 생성, 컴포넌트 실행, 상태 관리, 자식 렌더링이 한 곳에 있어서 어디서 문제가 생기는지 파악하기 어려웠습니다.

이번 구현: reconcile, mount, update, reconcileChildren 등으로 역할을 명확히 분리하니, 디버깅할 때 "아, 이건 reconcile의 update 부분 문제겠다"라고 바로 파악할 수 있었습니다. **"좋은 구조는 코드를 읽지 않아도 이해하게 만든다"**는 것을 체감했습니다.

2. 용어의 힘

처음 구현할 때는 "이전 것과 새것을 비교해서 DOM을 업데이트하는 함수"라고만 생각했는데, 이것이 Reconciliation이라는 명확한 용어가 있다는 것을 알고 나니, React 공식 문서나 다른 자료를 찾아볼 때도 훨씬 수월했습니다. 용어를 아는 것이 학습의 속도를 올려준다는 것을 깨달았습니다.

3. Map이 searchCurrentNode보다 훨씬 효율적

  • 이전: searchCurrentNode(key)로 전체 트리를 탐색해서 노드를 찾음 → O(n)
  • 이번: context.hooks.state.get(path) → O(1)

Map을 활용한 직접 접근이 얼마나 효율적인지, 그리고 자료구조 선택이 성능에 미치는 영향을 체감했습니다.


기술적 성장

이번 과제를 통해 좀 더 깊게 학습한 개념

  1. VNode와 Instance의 명확한 분리

    • 이전: ElementNode 하나로 설계도와 실제 DOM을 함께 관리 → 혼란
    • 이번: VNode(불변 설계도)와 Instance(가변 실제 객체) 분리 → 명확
    • Immutable과 Mutable을 분리하는 것의 중요성 학습
  2. Reconciliation의 4가지 시나리오

    - Unmount: node가 null
    - Mount: instance가 null
    - Replace: 타입/key 다름
    - Update: 타입/key 같음
    
    • 이전: if문으로만 처리해서 케이스가 불명확
    • 이번: 4가지로 명확히 분류하니 모든 상황을 체계적으로 처리
  3. Context 기반 중앙화된 상태 관리

    • 이전: 각 ElementNode에 state, stateCursor 프로퍼티로 분산 저장
    • 이번: Map<path, hooks[]> 구조로 중앙 관리
    • 상태를 찾기 위해 트리 탐색(O(n)) vs Map 조회(O(1))의 성능 차이 체감
  4. 마이크로태스크 스케줄링과 배치 처리

    • 이전: setState 호출마다 즉시 render → 비효율
    • 이번: withEnqueue로 여러 setState를 한 번의 render로 배치 처리
    • 브라우저 이벤트 루프와 마이크로태스크의 실전 활용
  5. 클로저의 실전 적용

    • 이전: "외부 변수를 기억한다" 정도만 이해
    • 이번: withEnqueuescheduled 플래그, useState의 hooks[cursor] 참조 등 실제 패턴을 직접 구현하며 클로저가 상태 관리의 핵심임을 깨달음

구현 과정에서 마주친 기술적 도전

  1. "The node to be removed is not a child" 에러

    • 문제: reconcileChildren에서 DOM을 중복으로 제거/삽입
    • 시도: anchor 기반 재배치, key 매칭, 역순 처리 등 복잡한 로직
    • 해결: reconcile에게 모든 DOM 조작을 위임, reconcileChildren은 매칭만
    • 배움: 복잡한 최적화보다 올바른 동작이 우선
  2. 중첩 컴포넌트 hooks 충돌

    • 문제: memo HOC에서 useRef가 이상하게 동작
    • 원인: 부모와 자식 컴포넌트가 같은 path 사용
    • 해결: 자식이 컴포넌트면 createChildPath로 고유 path 생성
    • 배움: 상태 격리를 위한 식별자 설계의 중요성
  3. cursor 초기화 타이밍

    • 문제: cursor.set(path, 0)을 명시적으로 하니 오히려 충돌
    • 원인: currentCursor getter가 이미 처리하는데 중복
    • 해결: 명시적 초기화 제거, getter에 책임 위임
    • 배움: 명확한 역할 분리와 추상화가 불필요한 로직 파악 및 제거에 도움이 됨

코드 품질

리팩토링이 필요한 부분

  1. DOM 속성 처리 중복

    • setDomProps와 updateDomProps에 이벤트/style 처리 로직 중복
    • 헬퍼 함수로 추출 가능 (applyEventHandler, applyStyle 등)
  2. 에러 경계 부족

    • 컴포넌트 실행 중 에러 발생 시 componentStack.pop() 누락 가능
    • try-catch로 Error Boundary 개념 적용 필요

코드 설계 관련 고민과 결정

  1. "책임 분리" vs "성능 최적화"

    • 고민: reconcileChildren에서 직접 DOM 조작하면 최적화 가능하지만 복잡
    • 결정: reconcile에 위임하는 단순한 방식 선택
    • 이유: 가독성과 유지보수성이 조기 최적화보다 우선
  2. "명시적 초기화" vs "Getter 신뢰"

    • 고민: cursor를 명시적으로 0으로 설정할지, getter가 알아서 하게 할지
    • 결정: getter 신뢰 (명시적 초기화 제거)
    • 이유: 중복 제거, 단일 진실 공급원(Single Source of Truth)

학습 효과 분석

가장 큰 배움

"아키텍처의 중요성"

이전 구현(coreV2)에서는 200줄짜리 render 함수를 디버깅하느라 고생했는데, 이번엔 처음부터 구조가 명확히 정의되어 있어서 각 Phase를 하나씩 완성해나가는 느낌이었습니다.

비교:

  • 이전: "어디에 뭘 추가해야 하지?" → 코드 전체를 읽어야 함
  • 이번: "reconcile의 update 부분에 추가하면 되겠다" → 바로 작업 시작

파일 구조만 봐도 차이가 명확합니다:

이전 (coreV2):
  └─ render/index.ts (244줄, 모든 로직 포함)

이번 (react):
  ├─ elements.ts (VNode 생성)
  ├─ reconciler.ts (비교 알고리즘)
  ├─ dom.ts (DOM 조작)
  ├─ hooks.ts (훅 시스템)
  ├─ context.ts (상태 관리)
  └─ render.ts (렌더 사이클)

Reconciliation 알고리즘

단 4가지 규칙(unmount/mount/replace/update)으로 모든 렌더링 케이스를 처리한다는 것이 놀라웠습니다. 복잡해 보이는 React도 결국 명확한 원칙의 조합이라는 것을 이해했습니다.

중앙관리식 Hooks의 장점

이전엔 각 노드에 state 배열을 저장했는데, 이번엔 Map<path, hooks[]> 구조로 중앙 관리하니:

  • 상태 격리가 명확해짐
  • 컴포넌트 언마운트 시 정리가 쉬워짐
  • 디버깅 시 전체 상태를 한눈에 볼 수 있음

배열과 인덱스라는 단순한 아이디어가, Map과 path와 cursor가 결합되어 강력한 시스템이 된다는 것을 깨달았습니다.

추가 학습이 필요한 영역

  1. 최적화 알고리즘: key 기반 자식 매칭의 최장 증가 부분 수열(LIS) 알고리즘
  2. Fiber 아키텍처: React 18+의 작업 분할, 우선순위 스케줄링, Concurrent 모드
  3. 서버 사이드 렌더링: VNode를 HTML 문자열로 변환, 하이드레이션

과제 피드백

과제에서 좋았던 부분

  1. 테스트 주도 학습

    • 각 단계마다 테스트 코드가 있어서 "내가 맞게 하고 있는지" 즉시 확인
    • 테스트 실패 메시지가 구체적이어서 디버깅에 큰 도움
    • 예: "expected 'initial' to be 'updated'" → setState가 안 되고 있다는 것을 바로 파악
  2. 명확한 구조 제시

    • 파일 구조, 함수 시그니처, 상세한 주석까지 제공
    • "무엇을 만들어야 하는지"가 명확해서 구현에 집중 가능
    • 이전 구현에서는 "어떻게 구조를 잡을까?"부터 고민해야 했음

과제에서 어려웠던 부분

  1. reconcileChildren 구현의 복잡도

    • key 매칭, anchor 처리, DOM 재배치가 처음엔 이해가 안 됨
    • 여러 번 시행착오 끝에 단순한 버전으로 결정
    • 더 좋은 예제 코드나 다이어그램이 있었으면 도움이 되었을 것 같음
  2. 디버깅 도구 부족

    • console.log만으로 path, cursor, hooks 상태를 추적하기 어려웠음
    • React DevTools 같은 시각화 도구가 있었으면 좋겠다고 생각

리뷰 받고 싶은 내용

1. Context 중앙화의 trade-off

이전 방식: 각 노드 객체에 상태 분산 저장

  • 장점: 노드만 있으면 바로 상태 접근
  • 단점: 메모리 많이 사용, 트리 탐색 필요(O(n))

이번 방식: Context의 Map으로 중앙 관리

  • 장점: O(1) 접근, 메모리 효율적, 정리 쉬움
  • 단점: context 의존성, 여러 루트 렌더링 시 복잡

여러 루트를 동시에 렌더링하거나 SSR을 지원하려면 Context를 함수로 생성하여 인스턴스화하는 것이 나을까요? 아니면 현재 전역 context 구조가 실무에서도 충분한 수준인가요?


4팀 코드 리뷰

- createElement: JSX -> VNode 변환 (Fragment 지원)
- nomalizeNode: 다양한 타입 정규화
- createChildPath: 컴포넌트 고유 경로 생성
- isEmptyValue: 렌더링 불필요 값 필터링
- context: root/hooks 상태 관리 및 getter 구현
- enqueue: 마이크로태스크 기반 비동기 실행
- withEnqueue: 중복 방지 스케줄러
- DOM 탐색 : getDomNodes, getFirstDom
- DOM 조작 : insertInstance, removeInstance
- 속성 관리 : setDomProps, updateDomProps
- 이벤트, style, className 처리
- setup: 초기 설정 및 컨텍스트 초기화
- render: 루트 렌더링 및 훅 정리
- reconcile: mount/update/unmount 처리
- reconcileChildren: 자식 배열 재조정
- useState: 상태 관리 및 리렌더링 트리거
- useEffect: deps 비교 및 비동기 실행
- cleanupUnusedHooks: 미사용 훅 정리
- flushEffects: effect 실행 트리거

- fix(dom.ts): input에 value속성 처리 개선
문제:
- 컴포넌트가 다른 컴포넌트를 반환할 때 같은 path 사용
- 부모 컴포넌트의 hooks와 자식 컴포넌트의 hooks가 충돌
- memo HOC 등에서 useRef와 useState가 같은 인덱스 사용

해결:
- 자식 노드가 컴포넌트면 createChildPath로 새 path 생성
- 자식 노드가 DOM이면 기존 path 유지
- mount와 update 모두 동일하게 처리
@joshuayeyo
Copy link

[Joshua] 4팀 코드 리뷰

역시 썸머언니🤔 코드 슬쩍 보는데도 군더더기 없이 깔끔하네요
집가서 남길 예정,,

@joshuayeyo
Copy link

언니 배포링크 빠졌어요 https://ahnsummer.github.io/front_7th_chapter2-2/

@joshuayeyo
Copy link

아래는 Senior React / Frontend Architecture 관점에서의 정석적인 답변이야.
지금 네 미니 React 구현이 “전역 context 하나로 훅 상태·VDOM 상태를 관리하는 구조”인데, 그 선택이 언제까지 유효한지, 어느 지점부터 설계를 바꿔야 하는지 명확하게 설명해줄게.


🔥 결론 먼저:

**“학습용 / 단일 루트 / CSR 전용이면 전역 context는 충분.

여러 루트, SSR, concurrent 렌더링이 보이면 → 반드시 context 인스턴스화를 고려해야 한다.”**

React도 결국 root 단위로 독립된 Fiber tree + Hook state container를 생성한다.
즉, *“전역 하나로 버티는 건 학습용 및 단일 루트 구조에서만 괜찮다”*는 게 실무 기준.


1. Context 중앙화의 Trade-off 분석 (네가 잘 정리해놨고, 조금 더 확장해줄게)

✔ 이전 방식: 노드 객체가 상태를 직접 가짐

  • 장점

    • 노드만 있으면 바로 해당 훅/상태 접근 가능
    • React 초기에 class 컴포넌트 구조와 유사한 방식
  • 단점

    • subtree 단위로 전부 탐색 필요 → 느림(O(n))
    • unmount 시 메모리 정리가 어렵고 분산됨
    • 렌더 사이드이펙트 증가

💡 학습용 구현에서는 오히려 직관적이지만, 확장성은 가장 낮은 방식.


✔ 현재 방식: 중앙 Context(Map) 기반 전역 상태 컨테이너

  • 장점

    • 훅 상태 조회/업데이트가 O(1)
    • 전체 훅 메모리가 한 곳에 있어 GC 및 초기화가 쉬움
    • 구조적 단순성 (Mini React 구현에서는 매우 적합)
  • 단점

    • 전역 의존성
    • 루트가 두 개 이상이면 state 충돌 가능
    • SSR 또는 concurrent-like 작업에서 race condition 발생

💡 즉, "싱글 루트 + 싱글 스레드 렌더링"이라는 전제에서만 안정적이다.


2. 핵심 질문:

🔍 여러 루트를 동시에 렌더링하거나 SSR을 고려하면?

전역 context는 바로 한계에 부딪힌다.

React가 root마다 별도 FiberRoot + Hook state container를 만들어온 이유.

왜냐면…

❗ 문제 1) 훅 커서/상태 충돌

두 개의 다른 트리가 동시에 render() 한다면:

context.cursor.set(path, index)

이 코드가 서로의 path를 오염시킨다.

❗ 문제 2) effect queue 충돌

전역 context.effects.queue에 섞여 들어가면서 순서, flush 타이밍이 꼬인다.

❗ 문제 3) SSR은 “요청마다 루트가 하나씩” 필요

전역 context를 쓰면 동시 요청 처리 불가능 → Memory leak & state contamination
(= Node 서버에서 절대 금지되는 패턴)

실제 React도 SSR에서 per-request root container를 생성한다.


3. 실무 기준: React는 왜 전역 context를 쓰지 않는가?

React의 구조는 다음과 같다:

FiberRoot {
  containerInfo: DOM or null
  current: FiberNode
  pendingLanes: ...
  effectList: ...
  hookStateContainer: ...
}

즉, **"root 단위로 완전히 분리된 상태 저장소"**가 있다.

그래서:

  • 여러 root 렌더링 가능
  • SSR에서 요청마다 FiberRoot 생성 가능
  • hydration 도중에도 문제 없음
  • concurrent 렌더링도 safe

4. 결론적으로:

🔥 “Context를 함수로 만들어 인스턴스화”가 맞다.

즉, 이런 형태:

export const createRenderContext = () => ({
  state: new Map(),
  cursor: new Map(),
  visited: new Set(),
  componentStack: [],
  effects: { queue: [] },
  container: null,
  node: null,
  instance: null,
});

그리고 render 단계에서:

const ctx = createRenderContext();
renderElement(rootVNode, rootContainer, ctx);

📌 장점

  • root마다 독립된 훅/상태 관리
  • SSR 시 요청 단위 context 생성
  • 병렬 렌더링 conflict 없음
  • 메모리 leak 위험 감소
  • 테스트 코드에서도 독립성 확보 (Jest 등)

📌 이 방식은 "React의 Fiber Root Container"와 동일한 철학.


5. 하지만… 정말 실무에서도 무조건 필요한가?

✔ 단일 루트 + CSR 전용 웹앱

전역 context 충분히 실무에서도 문제 없음
(리액트의 createRoot()도 기본적으로 싱글 루트로 쓰는 프로젝트가 많음)

✔ 하지만 아래 조건 중 하나라도 걸리면 반드시 인스턴스화해야 함

  • 다중 루트 렌더링(위젯, 포털, 모달 시스템 포함)
  • SSR/Hydration
  • concurrent-like scheduling 실험
  • 렌더링 중단/재개 가능성
  • 여러 isolate 환경 테스트 필요(jest 병렬 실행 등)

6. Senior React 관점의 조언

📌 “지금 단계에서 전역 context는 아주 합리적 선택이다.”

너의 목표가

  • 미니 React 구현
  • VDOM → DOM 패치
  • 훅 시스템, Reconciliation 이해

이거라면 전역 하나만 두는 게 구조 파악이 가장 직관적이고, 실습도 빠르다.

📌 하지만 설계가 어느 정도 안정화되면 반드시 root container로 context를 분리해라.

대신 다음과 같이 “context 인스턴스 전략”을 추천:

✔ 최소 단위: RootContext = 훅/인스턴스 상태 컨테이너

✔ 글로벌 단위: 환경 설정(옵션), 스케줄러(작업 큐) 정도만 전역 유지

이게 지금 React의 architecture와 가장 가깝다.


🔥 최종 한 줄 요약

전역 context는 “단일 루트 CSR 학습용”으로는 완벽한 선택.
하지만 다중 루트, SSR, concurrent 렌더링을 고려한다면
context를 함수로 만들어 루트 단위 인스턴스로 관리하는 것이
실무 React에 더 근접한 아키텍처다.


필요하면

  • createRoot 방식 구조
  • FiberRoot 대비표
  • SSR-safe 구조 설계도
  • 리액트 HookDispatcher 구현 관점 비교
    도 바로 만들어줄게.

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