-
Notifications
You must be signed in to change notification settings - Fork 49
[1팀 박용태] Chapter2-2. 나만의 React 만들기 #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
piggggggggy
wants to merge
54
commits into
hanghae-plus:main
Choose a base branch
from
piggggggggy:feature-spa-2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
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://piggggggggy.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 비교 기반 컴포넌트 메모이제이션구조 및 구현 소개
전반적인 구조와 동작의 흐름은 다음과 같습니다 :
VNode생성 (Virtual DOM)useState&useEffect)1.
VNode생성 (Virtual DOM)흐름의 시작은 개발자가 작성한 JSX를 Babel이
createElement함수로 변환시키면서 시작됩니다.최종적으로
createElement는VNode객체를 만들어냅니다.이렇게
createElement를 통해 다양한 형태의 요소들을 일관된 VNode 형태로 정규화합니다. 이 덕에 이후 로직은 정규화된 VNode 객체를 통해 진행됩니다.2. 초기 렌더링 (Setup & First Render)
앱이 시작될 때,
setup()함수가 호출됩니다.setup()함수의 역할을 다음과 같습니다.render()함수를 호출하여 첫 렌더링 작업을 시작합니다.3. Render Phase (Reconciliation)
render()함수 호출 후, 이 mini React의 핵심 단계인 재조정(Reconciliation) 단계가 시작됩니다. 이 과정은 다음과 같은 철학을 갖습니다.reconcile함수는 이를 위해의 순서로 작업을 진행합니다.
A. 비교 (Diffing)
변경된 부분만 찾아내기 위해 다음 세가지 인자를 받습니다.
B. 비교 결과에 따른 4가지 시나리오
Dffing 비교 결과에 따라,
domMutation)를 예약type이나key가 다름type과key가 같음reconcileChildren을 통해 자식들의 비교를 재귀적으로 수행C. 컴포넌트 실행
VNode의
type이 Function Component일 때,hookManager.runComponent()를 통해 실행reconciliation을 시작!!4. Commit Phase (실제 DOM 조작)
재조정(
reconciliation) 과정은 실제 DOM을 건드리지 않고, "무엇을 해야할지?"(domMutation)만mutationQueue에 추가합니다. 재조정이 끝나면, 그 때domMutationscommit이 시작됩니다.->
commitMutations(runtimeContext.workQueue.domMutations): queue에 추가된 Effects(INSERT, REMOVE, UPDATE_PROPS 등)를 순서대로 실행하여 한번에 실제 DOM에 반영합니다.5. Hooks 동작 원리 (
useState&useEffect)A. 전역 Context
컴포넌트에 작성되는 Hooks과 관련된 상태/정보를 각 컴포넌트의 맥락에 저장하지 않고, 전역의 Context라는 전역 저장소에 저장합니다.
Key(component path) : Value(state[], effect[])형태로 저장.B. Cursor 기반 접근
컴포넌트가 실행될 때, 내부의 훅들은 순서대로 실행됩니다.
예시 :
useState호출 -> state 배열의 0번 인덱스로 저장useEffect호출 -> effects 배열의 0번 인덱스로 저장useState호출 -> state 배열의 1번 인덱스로 저장(+) 원래 state와 effects를 따로 저장하고, 각각의 Cursor도 따로 운용했으나, 리팩터링하며 하나의 Hooks Map에 저장하고 Cursor도 하나로 통일하여 운용하는 것으로 변경
C. 상태 변경 (
setState)setState가 호출되면 :enqueueRender()를 호출하여 리렌더링을 예약!6. Scheduler (비동기 Baching 렌더링을 통한 성능 최적화)
setState를 5번 연속 호출한다고 해서 render가 5번 발생하지 않음.enqueueRender():queueMicrotask를 사용해 렌더링 작업을 마이크로태스크 �큐에 넣음Batching: 자바스크립트 콜 스택이 비워질 때까지 기다렸다가, 한 번에 가장 최신의 상태로 딱 한번만render()를 실행시킴.7. 사후 처리 (Cleanup & Effects)
렌더링과 DOM 업데이트가 모두 끝난 직후 :
Cleanup: 언마운트된 컴포넌트나, 의존성이 바뀐 Effect의 Cleanup 함수를 실행함.useEffect실행 : 새로 등록된 Effect 함수들을 실행함.전체 동작 요약
setState호출 -> 상태 업데이트 ->enqueueRender예약render()실행domMutations큐에 추가useEffect실행개선
결론적으로 이런 개선이 이뤄졌습니다.
render.ts) -render함수가 파이프라인 역할을 하는 것처럼 보이도록 !!!context.ts) -storeContext와runtimeContext로 분리!commit.ts,scheduler.ts) -reconcile함수는 이제 **"계산"**에만 집중하고, 반영은 전담 모듈이!!!1. Context 구조 개선
Context의 구조를 **"영속 상태"**와 **"일시적 상태와 작업"**으로 분리하는 것으로 방향을 잡았습니다.
context객체에 모든 상태와 정보를 관리하는 것 대신,StoreContext(영속) : 렌더링이 끝나도 기억해야 하는 값들 - Hook 상태, 현재 VDOM, Instance 등RuntimeContext(일시적) : 렌더링 중에만 사용되고, 매 프레임마다 초기화되는 값들 - 커서, 큐 등2. Render Pipline 개선
render함수가 전체 생명주기의 목차역할을 할 수 있도록 추상화 및 정리를 진행했습니다.render함수만 봐도준비 -> 재조정 -> 반영 -> 이펙트의 흐름이 한눈에 들어올 수 있도록 구성했습니다.3. Commit 로직의 분리와 투명화
reconcile내부에서domEffects를 추가하는 구조로 디자인했지만, 수집된domEffect들이 어떻게 처리되는지 를 매끄럽게 확인하기 어려웠습니다.따라서, 이것이 어떻게 처리되는지를
commit.ts모듈로 분리했습니다.4. Effect Scheduler 정리
기존의 복잡했던 이펙트 처리(old 정리 후 new 실행) 로직을,
scheduler라는 이름으로 묶어 명확히 수행하는 방향으로 개선했습니다.과제 회고
작업의 흐름과 고민들...
실제 작업 시간보다는 공부하고 이해하는 시간이 월등히 길었습니다. 결국에는 Mini React를 구현하는 과정이기 때문에, React의 구조에서 사고를 해야한다고 생각했는데, 그렇다보니 실제 React의 동작원리와 철학에 대한 이해 없이는 시작하기가 너무 어려웠습니다.
처음에는 내가 이 과제를 통해 구현해야하는 것은 무엇인가? 라는 고민부터 시작했습니다.
createElement구현은
createElement의 구현부터 시작했습니다.처음에 상당히 어렵고 답답했는데, "내가 왜 이걸 하고 있지? 이게 필요한 이유는 뭐지? 뭐가 이해 안되는거지?" 이런 고민을 하다보니, JSX와 JSX가 변환되는 과정에 대한 이해가 부족하다는 것을 깨닫고, 실제 JSX가 Babel을 통해 어떻게 변환이 되는지 부터 분석했습니다. 그과정에서 Babel Playground를 찾아내 아주 많은 도움을 받았습니다.
그 과정에서
"그냥 배열, 같은 빈 객체 타입이나, 원시타입은 어떤 형태의 VNode로 만들어져야할까?"
"createElement가 VNode로 변환하는 과정에서 []이나 {}는 어떻게 처리할까? 그리고 빈 객체가 아닌 채워진 객체라면?"
"Fragment는 전용 vnode가 있다. 이것을 돔을 수정할 때 계산한다. -> 왜?"
이런 질문을 던지고, 해소하는데도 적잖은 시간을 썼습니다..
구조 개념잡기 (Context, Instance, Virtual DOM, Real DOM)
"React는 Virtual DOM을 활용하여, 변경된 부분만 Real DOM에 반영한다." 정도는 이제 FE개발자로서,, 알기는 합니다만, React를 실제로 구현하는 과정에서의 Virtual DOM을 이해하는 것은 아예 다른 차원의 이해였습니다.
"왜 Virtual DOM을 사용하나, VIrtual DOM의 역할은?" 부터 시작해. React 엔진의 원리와 구조를 코드 레벨에서 이해애야했습니다.
동작 개념잡기 (Setup, Render, Reconcile, DOM 조작 그리고 Life Cycle)
내부에 존재하는 함수들과, 그 역할 그리고 실제 각 라이프 사이클에서 어떤 파이프라인으로 동작하는지 이해하는데도 적잖은 시간을 사용했습니다.
이 과정에서 정말 많은 역할들이 있지만, 그게 "정말 필요해서 있다"를 이해하기 까지 머리가 너무 아팠습니다. 정규화된 VNode를 기존 인스턴스의 VNode와 비교하고,, 그것을 또 재귀적으로 수행하고,, 그 과정에서 Instance의 dom에 붙이고,, 그러다보니 더 헷갈려서, reconcile에서 dom 조작의 관심사를 아예 분리해서 domEffect (현 domMutation) 과정을 새로 만들어내고.... 뇌가 무한 재귀에 빠졌다가 나왔다가를 반복하는 시간이었습니다.
실제 작성했던 고민들
useState와useEffect,cursor아... 지금 생각해도 아찔합니다. 단순히 지난주차의 수준이 아니었습니다ㅠㅠ 지난주에 집중했던 포인트는,
state의 변경이 어떻게 컴포넌트의 리렌더를 트리거할 것 인지?에 집중했다면, 이번에는 그 사고를 React 엔진의 관점으로 확장해야했습니다. 상태 값과 deps의 저장, 다음 렌더 시 그 값을 어떻게 다시 찾아내 참조할 것인지, Cursor가 정답일까? 뭔가 조악한데... 등의 고민이 핵심이었던 것 같습니다.처음에는 Cursor와 Hook Map을
state와effect따로 운용했지만, 나중에는 위 과정에서의 이해를 통해 이를 하나의 Cursor와 Hooks로 통합 관리할 수 있게 되었습니다...아하! 모먼트 (A-ha! Moment)
막연하게만 알고 있던, "hook은 왜 조건문 안에서 사용하면 안될까?"를 명확하게 알게 되었습니다.
직접 Cursor 기반의 Hook Context 관리 구조를 이해하고 디자인 하면서, 조건부 호출 때문에 Cursor Index가 오염된다면, 의도치 않은 상태와 이펙트가 사용될 수 있겠구나! 이해할 수 있었습니다.
심화 과제에서 추가 훅을 구현하는 과정이 대부분 아-하! 모먼트였던 것 같습니다. 기존에는 hook을 사용할 때, "이건 이런 상황에 사용해야해", "이 훅은 이런 부분을 메모이제이션 해줘!" 수준의 이해에서 그쳤다면, "이건 이런 메모이제이션을 하는 훅이니까(기존 지식 수준), 이 React의 구조에서는 이런 부분이 캐싱이되면 실제로 기대하는대로 동작할 수 있겠다!"로 확장되는 경험을 했습니다. (적고나니 무슨말인가 싶긴하네요..)
생각나면 추가하겠습니다...
학습과 성장
과제를 마친 지금 시점에서 생각해보면,,, 기대 이상의 학습과 경험이었습니다.
내가 무에서 유를 만든 것은 아니고, 이미 잘 만들어진 구조를 재건축해보는 일을 한 것일 뿐이지만..
그렇기에 명확한 큰 목표 아래서 굉장히 밀도있게 문제 해결을 해보고 그것을 잘 돌아가는 하나의 시스템으로 만들어내기 위해 다양한 고민과 경험을 해봤다는 느낌을 받습니다. 특히 심화 과제의 훅들을 만들고 테스트하는 과정에서 그전의 과정과 달리 빠른 시간안에 문제를 해결하고 테스트가 통과되는 경험을 했고, 이 경험이 "내가 잘 만들어 냈구나!" 라는 효능감이 폭발했던 시점이었습니다.
과제의 성공을 떠나서, 이번 경험을 통해 얻은 가장 큰 통찰은
"React도, Component도, Virtual DOM도, Hook들도 결국에는 Magic이 아닌 문제해결의 결과물이구나" 라는 생각이었습니다. 오랫동안 React나 Vue같은 도구 위에서 작업하다보니 굳어진 사고의 울타리가 깨지는 좋은 경험이었습니다.
리뷰 받고 싶은 내용