[5팀 오태준] Chapter 2-2. 나만의 React 만들기#42
Open
taejun0 wants to merge 9 commits intohanghae-plus:mainfrom
Open
Conversation
…o, useAutoCallback)
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://taejun0.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 비교 기반 컴포넌트 메모이제이션심화 과제 완료 기준:
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를 계산하는 것이었습니다:
nextChildren을 역순으로 순회하면서 각 자식의 anchor를 계산insertBefore호출 시,currentDom이 이미anchor.previousSibling이 아니거나, 첫 번째 위치가 아니면 무조건 이동특히
b가 첫 번째로 이동할 때,currentDom.nextSibling === anchor인 경우에도currentPrevSibling !== null이면 이동해야 한다는 점을 깨달았습니다. 이는 DOM이 이미 올바른 상대 위치에 있어도, 절대 위치가 다르면 이동이 필요하기 때문입니다.컴포넌트 경로 변경과 상태 마이그레이션:
Footer컴포넌트의 상태가Item개수 변경 시 유지되지 않는 문제가 있었습니다. 원인은 컴포넌트의 경로(path)가 변경되면hooks.state와hooks.cursor가 새 경로에 없어서 상태가 초기화되는 것이었습니다.처음에는 경로 계산 로직을 수정하려고 했지만, 실제 문제는 경로 변경 시 상태를 마이그레이션하지 않는 것이었습니다.
해결책은
updateComponent에서 경로가 변경될 때:oldPath에서hooks.state,hooks.cursor,hooks.visited를 읽어옴newPath로 복사oldPath의 데이터를 삭제context.hooks.cursor.set(path, 0)호출 전에 수행이렇게 하면 경로가 변경되어도 컴포넌트의 상태가 유지됩니다.
Fragment의 DOM 노드 수집과 제거:
{hasChildren && <>...</>}에서hasChildren이 false가 되면 Fragment의 자식들이 제거되어야 하는데, 계속 남아있었습니다.문제는
getDomNodes함수가FRAGMENT와COMPONENT를 제대로 처리하지 못했던 것입니다.instance.dom을 직접 반환하면 Fragment는 DOM이 없어서 빈 배열이 반환되었습니다.해결책은
getDomNodes에서:FRAGMENT와COMPONENT는instance.dom이 없으므로 재귀적으로 자식들의 DOM을 수집NodeTypes.FRAGMENT와NodeTypes.COMPONENT를 명시적으로 비교 (이전에는instance.kind === 'FRAGMENT'로 비교했는데 타입 오류 발생)updateComponent에서childNode가 null이거나 빈 children일 때,prevChild가 Fragment면 명시적으로reconcile(parentDom, prevChild, null, path)호출이렇게 하면 Fragment의 모든 자식 DOM이 재귀적으로 제거됩니다.
기술적 성장
Reconciliation 알고리즘의 복잡성 이해:
이번 과제를 통해 Reconciliation이 단순히 "이전과 현재를 비교"하는 것이 아니라, 여러 전략을 조합해야 한다는 것을 깊이 이해했습니다:
또한 anchor를 사용한 DOM 재배치 로직에서:
insertBefore의 동작 원리와previousSibling의 관계디버깅 전략의 중요성:
문제가 계속 해결되지 않을 때, "같은 방식으로 계속 시도"하는 것보다는:
console.log를 추가해 실행 흐름 추적특히
console.log를 전략적으로 배치해, 어떤 함수에서 문제가 발생하는지 정확히 파악한 것이 큰 도움이 되었습니다.상태 관리와 경로의 관계:
React의 Hook 시스템이 컴포넌트 경로에 의존한다는 것을 직접 구현하며 이해했습니다:
hooks.visited를 사용해 현재 렌더링에서 방문한 컴포넌트만 추적cleanupUnusedHooks에서 방문하지 않은 컴포넌트의 상태를 정리코드 품질
특히 만족스러운 구현:
리팩토링이 필요한 부분:
reconcile함수가 1000줄이 넘어 가독성이 떨어집니다.mountHost,updateHost,mountComponent,updateComponent등을 별도 파일로 분리하면 좋을 것 같습니다.updateHost와updateFragment의 로직이 거의 동일하므로, 공통 로직을 추출해 중복을 제거할 수 있습니다.console.log가 많이 남아있어 제거가 필요합니다.코드 설계 관련 고민과 결정:
reconcile함수에 anchor 파라미터를 추가했습니다. 이는 재배치 로직을 크게 개선했지만, 함수 시그니처가 복잡해졌습니다."0.c0.i1.c2")를 사용해 계층 구조를 표현했습니다. 디버깅에 유용하지만, 경로 변경 시 마이그레이션이 필요해 복잡도가 증가했습니다.NodeTypes.FRAGMENT와 같은 상수를 사용해 타입을 비교했습니다. 이는 타입 안정성을 높이지만, 런타임 비교가 필요합니다.학습 효과 분석
가장 큰 배움이 있었던 부분:
insertBefore와removeChild를 사용할 때, 노드의 현재 위치와 부모를 정확히 확인해야 합니다.추가 학습이 필요한 영역:
any타입을 사용하는 부분을 더 엄격한 타입으로 개선할 수 있을 것 같습니다.실무 적용 가능성:
과제 피드백
과제에서 모호하거나 애매했던 부분:
createChildPath함수의siblings파라미터: 실제로는 사용되지 않았는데, 시그니처에 포함되어 있어 혼란스러웠습니다.{hasChildren && <>...</>}에서hasChildren이 false일 때 Fragment가 null이 되어야 하는지, 빈 Fragment로 처리해야 하는지 명확하지 않았습니다. 테스트를 통해 null이 되어야 함을 확인했습니다.과제에서 좋았던 부분:
리뷰 받고 싶은 내용
Reconciliation 로직의 구조 개선 방안:
현재
reconcile함수가 1000줄이 넘어 가독성이 떨어집니다.mountHost,updateHost,mountComponent,updateComponent등을 별도 파일로 분리하는 것이 좋을까요? 아니면 현재처럼 하나의 파일에 유지하는 것이 더 나을까요?또한
updateHost와updateFragment의 로직이 거의 동일한데, 공통 로직을 추출해 중복을 제거할 수 있을까요? 어떤 방식으로 리팩토링하면 좋을지 조언해주실 수 있을까요?컴포넌트 경로 관리 전략:
현재 컴포넌트 경로를 문자열(
"0.c0.i1.c2")로 표현하고 있는데, 경로 변경 시 상태 마이그레이션이 필요합니다. 더 효율적인 경로 관리 방식이 있을까요?예를 들어:
어떤 방식이 더 나을지 조언해주실 수 있을까요?
anchor 계산 로직의 최적화:
현재 anchor 계산 로직이 복잡하고, 역순 순회를 사용하고 있습니다. 더 효율적이거나 명확한 방법이 있을까요?
특히 key 기반 재배치에서:
insertBefore호출 조건이 부분들을 더 간결하게 표현할 수 있는 방법이 있을까요?
타입 안정성 개선:
현재
any타입을 일부 사용하고 있는데, 더 엄격한 타입 정의로 개선할 수 있을까요?특히:
normalizeNode함수에서any를 사용하는 부분createElement의 children 타입string | symbol | React.ComponentType인데, 이를 더 구체적으로 타입화할 수 있는 방법이런 부분들을 어떻게 개선하면 좋을지 조언해주실 수 있을까요?
디버깅 전략:
문제가 계속 해결되지 않을 때,
console.log를 추가해 디버깅하는 방식 외에 더 효과적인 방법이 있을까요?예를 들어:
어떤 방식이 더 효율적일지 조언해주실 수 있을까요?