[3팀 김준모] Chapter 2-2. 나만의 React 만들기 #54
Open
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://jumoooo.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 비교 기반 컴포넌트 메모이제이션과제 셀프회고
아하! 모먼트 (A-ha! Moment)
JSX와 createElement의 관계: 테스트 코드에
createElement가 없는데 어떻게 통과되는지 의문이었는데, JSX가 내부적으로createElement를 자동 호출한다는 사실을 깨닫고 이마를 탁 쳤습니다. 이는 Babel이 JSX를createElement호출로 변환하는 트랜스파일 과정을 이해하는 중요한 순간이었습니다.VNode(청사진) vs Instance(실체): 처음엔 두 개념이 혼동되었으나, VNode는 설계도이고 Instance는 실제 DOM, children, key 등을 들고 있는 현장의 실체라는 개념이 잡히면서 Reconcile 로직을 정리할 수 있었습니다. 특히
reconcile함수에서 기존 Instance와 새 VNode를 비교해 '버릴지, 고칠지, 새로 만들지' 판단하는 최적화 로직을 직접 구현해 본 것이 가장 큰 깨달음이었습니다.Hook의 전역 관리: 지난 과제에서 상태 관리를 전역에서 해야 한다고 생각해서 뺐었는데, 실제로 React도 Context나 Hook(Map, Set 등)을 전역 컨텍스트에서 관리한다는 것을 확인하고 "내 예상이 틀리지 않았구나!" 하는 확신을 얻었습니다. Path 기반 상태 격리 시스템을 통해 각 컴포넌트의 훅 상태를
Map<string, Hook[]>형태로 관리하는 구조를 이해하게 되었습니다.Fragment와 DOM 평탄화:
<>(Fragment)나 컴포넌트는 실제 DOM이null이기 때문에, 왜 부모가 자식 노드를 찾을 때 'DOM 평탄화' 과정이 필요한지, 그리고 왜insertBefore같은 로직이 중요한지 화면 렌더링 순서 버그(Footer가 위로 올라가는 현상)를 고치며 깊이 이해했습니다. Fragment는 자체 DOM이 없으므로 자식들을 재조정할 때 부모 DOM을 사용해야 한다는 점을 배웠습니다.기술적 성장
함수형 컴포넌트의 처리: 일반 태그와 달리 함수형 컴포넌트는 children을 직접 가지는 게 아니라, 함수 실행 결과(props 전달)를 렌더링해야 한다는 점을 테스트 실패를 통해 배웠습니다. 더 중요한 것은 함수형 컴포넌트의 자식이 부모와 독립된 고유한 경로를 가져야 한다는 점이었습니다.
reconcile함수에서createChildPath를 사용하여 자식의 고유 경로를 생성하도록 수정하여 Path 충돌 문제를 해결했습니다.DOM 렌더링 순서 제어: E2E 테스트 중 Footer, Header, Main의 순서가 뒤죽박죽 섞이는 문제가 발생했습니다. 원인은 key 기반 인스턴스 매칭은 올바르게 작동하지만, DOM 순서 재배치 로직이 누락되어 있었기 때문입니다. 역순으로 순회하여 다음 인스턴스의 첫 DOM 노드를 anchor로 사용하는
insertBefore전략을 구현하여 해결했습니다. 이 과정에서 DOM 노드를 물리적으로 이동시켜야 한다는 점을 이해하게 되었습니다.useRef와 리렌더링:
useRef가 단순한 변수가 아니라, 리렌더링 되어도 버려지지 않는 '보관함'이며, 내부 값을 바꿔도 보관함 자체는 그대로라 리렌더링을 유발하지 않는다는 원리를 명확히 했습니다.useRef는 lazy initializer 패턴을 사용하여 최초 한 번만 초기값을 평가하고, 이후에는 같은 참조를 반환하여 값 보존과 리렌더링 방지를 동시에 달성합니다.이벤트 위임 패턴: 이벤트 시스템을 구현하며 이벤트 위임(Event Delegation) 패턴을 깊이 이해하게 되었습니다. 모든 이벤트 리스너를 루트 컨테이너에 한 번만 부착하고, 실제 이벤트 발생 시 타겟 요소를 찾아 핸들러를 실행하는 방식입니다. React DOM 스타일로
createRoot시점에 이벤트 루트를 설정하고, 전역 이벤트 레지스트리를 통해 이벤트 타입을 관리하는 구조를 구현했습니다.useEffect cleanup 실행 조건: 무한 스크롤 기능이 작동하지 않는 문제를 디버깅하며,
useEffect의 cleanup이 의존성 변경 없이도 실행되는 문제를 발견했습니다. cleanup은shouldRunEffect가true일 때만 실행되어야 하며, 의존성이 변경되지 않았으면 기존 훅을 유지하여 cleanup 함수를 보존해야 한다는 점을 배웠습니다.Path 기반 상태 격리: 중첩된 컴포넌트에서
useState가 각각 독립적으로 동작하도록 Path 충돌을 방지하는 메커니즘을 구현했습니다. 타입이 다른 컴포넌트가 같은 path를 사용하지 않도록 타입 식별자를 추가하여 고유한 path를 생성하는 로직을 추가했습니다.코드 품질
만족스러운 구현:
Path 충돌 방지 로직: 타입이 다른 컴포넌트가 같은 path를 사용하지 않도록, 타입 식별자를 추가하여 고유한 path를 생성하는 로직을 구현했습니다. 이를 통해 중첩된 컴포넌트에서
useState가 각각 독립적으로 동작하도록 보장했습니다.함수형 컴포넌트 Path 수정:
createChildPath를 사용하여 함수형 컴포넌트의 자식이 부모와 독립된 고유한 경로를 갖도록 수정하여memoHOC가 정상적으로 동작하도록 했습니다.리팩토링 필요:
window.__REACT_DEBUG_EVENTS__플래그를 활용해 로그를 찍어보고 있지만, 더 체계적인 디버깅 도구가 필요할 수 있습니다. 이벤트 시스템의 상태를 시각화하거나 이벤트 흐름을 추적하는 도구가 있다면 추가해 보고 싶습니니다.설계 관련:
.cursor/mockdowns/study폴더로 분리하고, 실제 구현 과정과 문제 해결 내용은.cursor/mockdowns/react-implementation에서 진행하여 학습과 구현의 경계를 명확히 하려 노력했습니다. 이를 통해 추후 교육 자료로 활용할 수 있도록 구성했습니다.학습 효과 분석
가장 큰 배움:
Reconcile 과정에서 기존 Instance와 새 VNode를 비교해 '버릴지, 고칠지, 새로 만들지' 판단하는 최적화 로직을 직접 구현해 본 것이 가장 컸습니다. 특히 타입이 다른 컴포넌트가 같은 path를 사용하지 않도록 Path 충돌을 방지하는 메커니즘을 구현하며, 컴포넌트 트리 구조와 상태 격리의 관계를 깊이 이해하게 되었습니다.
함수형 컴포넌트의 Path 충돌 문제를 해결하며, 각 컴포넌트가 트리 구조에 따라 고유한 경로를 부여받아야 훅 상태가 완전히 격리된다는 점을 배웠습니다.
createChildPath를 사용하여 자식의 고유 경로를 생성하는 것이 얼마나 중요한지 깨달았습니다.추가 학습 필요:
이벤트 시스템을 구현하면서 무한 스크롤 기능 등에서 이벤트가 제대로 등록/해제(cleanup)되지 않는 문제를 겪었습니다. React의 합성 이벤트(Synthetic Event)나 이벤트 위임 방식에 대해 더 깊은 학습이 필요함을 느꼈습니다. 특히 JSDOM 호환성 문제를 해결하며 네이티브 이벤트를 직접 사용하는 방식으로 수정했지만, 실제 React의 Synthetic Event 구현 방식을 더 자세히 공부하고 싶습니다.
과제 피드백
과제에서 좋았던 부분:
테스트 코드가 처음엔 난해하게 느껴졌습니다. 특히 container 내부가 어떻게 변하는지(예:
childNodes길이 등)를 검증하는 로직을 이해하는 데 시간이 걸렸지만, 덕분에 DOM 조작의 결과를 확실히 알 수 있었습니다. 테스트 코드를 통해 구현이 올바른지 검증할 수 있어 안정감을 느꼈습니다.React 내부 구현체의 복잡함을 단계별로 쪼개서 구현해 볼 수 있는 점이 좋았습니다. Phase별로 나누어져 있어 단계적으로 학습하고 체계적으로 구현할 수 있었습니다.
과제에서 모호하거나 애매했던 부분:
초기에는 JSX와
createElement의 관계가 명확하지 않아 혼란스러웠지만, Babel의 트랜스파일 과정을 이해하고 나니 자연스럽게 해결되었습니다. 이는 학습 과정에서 자연스럽게 해결되는 부분이었습니다.리뷰 받고 싶은 내용
이벤트 리스너 관리 및 디버깅
무한 스크롤 구현 시 이벤트가 중간에 사라지거나 중복 등록되는 이슈가 있었습니다. 문제의 원인은
useEffectcleanup이 의존성 변경 없이도 실행되는 것이었고, 이를 해결하기 위해 cleanup은shouldRunEffect가true일 때만 실행되도록 수정했습니다.또한
updateDomProps에서 이벤트 핸들러를 등록하기 전에Object.is(prevValue, nextValue)체크를 먼저 수행하여 함수 참조가 같으면 이벤트 핸들러 등록 로직을 건너뛰는 문제를 발견했습니다. 이벤트 핸들러 처리 로직을Object.is체크 이전으로 이동하여 항상 재등록하도록 수정했습니다.현재
window.__REACT_DEBUG_EVENTS__플래그를 활용해 로그를 찍어보고 있는데, 실제 프레임워크 레벨에서 이벤트를 디버깅하거나 관리할 때 주로 사용하는 패턴이나 팁이 있을까요? 특히 이벤트 위임 환경에서 이벤트 핸들러가 제대로 등록/해제되는지 확인하는 방법에 대해 조언을 받고 싶습니다.