[2팀 박소연] Chapter 1-2. 프레임워크 없이 SPA 만들기#36
Open
soyalattee wants to merge 33 commits into
Open
Conversation
- text + "" 과 text.toString() 중 고민
해당 케이스는 if문으로 type을 구분하기때문에 상관 없지만, toString()의 경우 null/undefined일경우 에러가 남으로 안정성 측면에서 +""를 하는걸로 선택
text + "" 시 내부적으로 text를 primitive로 변환후 문자열 연산을 한다.
- 자식이 함수일때 정규화
vNode가 function 으로 들어올거라 예상함.
초기 코드
```
if (typeof vNode === "function") {
return normalizeVNode(vNode());
```
근데 vNode <= 이므로 함수가 아니라 object 형태가 들어오게됨.
vNode가 object 고 그 type 값이 function인 형태
normalize로 들어오는 값은
```
createVNode(TestComponent, null) // 하면
{
type: TestComponent, // 함수가 여기 들어감
props: null,
children: []
}
```
그래서 type이 function 인지 체크후, props와 children 값을 넣어 함수 실행
=> createVNode 함수가 실행됨.
여기서 의문. 그럼 type값은 언제 세팅돼??
해결 :
```
//해당 JSX함수는 안에 또 element를 가지고있잖아.
const UnorderedList = ({ children, ...props }) => (
<ul {...props}>{children}</ul> // ← 여기서 type: "ul" 결정!
);
```
처음, vNode 가 들어온 후 이 함수가 뭘 return해주는건지 몰라서 헤멤 테스트케이스를 보고 el를 내보내야한다는걸 깨달음 근데 throw new Error는 안해줘도 테스트케이스가 통과되는거같다 createElement에서는 각 node를 불필요한(falsy값)이면 빈문자열화, 배열이면 DocumentFragment화 한 후 , element를 생성해서 내보낸다.
eventRegistry 배열을 만들어 이벤트를 이곳에 등록하고, setup시 root엘리먼트에 등록되도록 함 두번째 테스트코드가 실행되며 기존 container를 제거하고 새로운 컨테이너를 붙히는작업을하는데(beforeEach, afterEach) 이때 eventRegistry가 비워지지않아서 두번째 테스트에서도 이미 이벤트가 붙어있는 문제 발생 => setup시 eventRegistry를 정리하는 작업 추가. filter를 사용해 document에 해당 element가 없으면 제거
P&G 다우니 -> P&G 다우니 여야한다고 ...실패함.. 그래서 강제로 &를 & 로 수정하는 escapeUtil 함수 만들고 create 함수에서 elinnerHTML 할때 child 붙힐때 해당 유틸 사용. ㅠㅠ
eventRegistry를 배열에서 WeakMap 으로 수정 배열로하면 삭제가 제대로 안된다 윤우님과 태영님의 조언으로 해결
문제: innerHTML +=를 사용하면 기존에 appendChild로 추가한 DOM 요소들이 다시 파싱되면서 이벤트 핸들러가 모두 사라진다고한다.
This reverts commit 9e5429b.
This reverts commit b081616.
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://soyalattee.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
이벤트 위임
심화 과제
Diff 알고리즘 구현
과제 셀프회고
기술적 성장
Virtual DOM 과 vNode 에 대한 이해
처음 createVNode 에서 노드가 함수형태로 들어올것이라 예상. 그리고 함수를 풀어서 object화 해야한다고 생각했는데 object 형태로 들어오고 있었고, type 안에 function 이들어오는 구조. 그럼 type 값은? 이란 의문을 가짐.
해결 :
JSX함수는 안에 또 element 가 될 노드를 가지고있으니, 재귀형태로 vNode를 생성 가능.
과제를 통해 JSX → createVNode → normalizeVNode → createElement의 흐름을 직접 구현하며 Virtual DOM의 작동 원리를 좀 깊게 이해할 수 있었습니다. 특히 함수형 컴포넌트가 재귀적으로 vNode 트리를 생성하는 방식에서 type이 함수인 경우의 처리와, children을 props에 포함시켜 다시 렌더링하는 구조를 구현하며 JSX의 변환 과정을 깊게 이해했습니다.
이벤트매니저를 통해 메모리 관리
초기에는 eventRegistry를 배열로 관리하며 메모리 해제를 시도했으나, 테스트 환경에서 DOM 제거 후에도 이벤트가 남아 있는 문제가 발생
테스트코드 내에서 기존 container를 제거하고 새로운 container를 붙히는데 (beforeEach, afterEach) 이때 eventRegistry가 비워지지않아, 다음 테스트에서 이미 이벤트가 붙어있는 문제 발생
해결 :
기존 코드
수정된 코드
WeakMap 기반으로 element → Map(eventType → Set(handler)) 구조를 갖도록 했고, root에서 버블링을 타고 올라가며 최적의 핸들러를 찾아 실행하는 방식으로 재설계했습니다.
구조로 수정하며 테스트 환경에서도 이벤트 중복, 누수 없이 안정적으로 작동하도록 구현할 수 있었고, 메모리 관리와 성능 개선에 대한 감각을 좀 얻었습니다.
document 의 이해
문제: innerHTML +=를 사용하면 기존에 appendChild로 추가한 DOM 요소들이 다시 파싱되면서 이벤트 핸들러가 모두 사라지는 이슈 발생
해결: 자식노드 추가를 appendChild로 코드수정
해결을 위해 appendChild 방식으로 DOM을 조작하며, DOM 파싱의 작동 방식과 이벤트 핸들러 보존의 중요성을 체감했습니다.
코드 품질
과제를 빠듯하게 마무리하여 전체적인 리팩토링 여지가 많은 상태로 마무리되었습니다..
특히 createElement와 updateElement에서 요소의 속성을 설정하는 부분에서 반복되는 로직이 많았고, 조건 분기도 지나치게 세분화되어 있어 중복 제거 및 함수 분리가 필요하다고 느꼈습니다. (특히 updateAttribute 와 createAttribute의 중복코드 다수)
이후 작업에서 이런 공통 속성 처리 로직을 별도 유틸로 추출하고자 합니다.
학습 효과 분석
이번 학습을 통해 프레임워크가 제공하는 추상화 뒤에 숨겨진 핵심 렌더링 원리와 이벤트 관리를 직접 다뤄보면서 실무에서 발생할 수 있는 문제 상황에서도 내부 동작을 추론하고 디버깅할 수 있는 근거를 마련할 수 있었습니다.
과제 피드백
저번 과제보다 양이 적은줄 알고, 금방하겠지? 했는데 오산이였습니다.
개인적으로 저번과제보다 더 어려웠습니다.. 기초가 부족해서 그런거라 생각됩니다.
그런의미에서 너무 좋은 과제였고 이번에도 머리 싸매면서 즐겁게 코딩했습니다. 감사합니다.
리뷰 받고 싶은 내용
렌더링 흐름에서 renderElement 함수가 최초 렌더링과 업데이트 렌더링을 구분하기 위해 container._prevVNode를 직접 추가하여 상태를 유지하는 방식을 사용했습니다.
기존코드
하지만 이 방식으로 렌더링을 반복할 경우, 특정 상황에서 자식 엘리먼트들이 의도하지 않은 부모로 이동하거나, 기존 자식이 새로운 부모 아래로 재배치되는 버그가 발생했습니다.
ex)
초기 DOM 구조
업데이트시 DOM 구조(잘못된 업데이트)
second-node 아래에 first-node 자식들이 들어가 버리는 등의 비정상적인 구조가 되었습니다...!
오랫동안 updateElement 과정에서 헤메다가 혹시 렌더링시에 꼬이나 싶어 기존 노드 저장방법을 변경했습니다.
WeakMap()인 currentNodeMap을 만들어 사용하니 해결되었습니다.
💡 궁금한 점
container._prevVNode처럼 단일 ref로 상태를 관리하는 방식은 왜 문제를 일으켰을까요?
반면 WeakMap을 이용하니 문제가 해결된 원인은 무엇일까요?