과제 체크포인트
배포 링크
https://eveneul.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
🗺️ map VS Weakmap
- 과제를 시작하기 전, 학습메이트분께서 어디에선가
Weakmap을 사용할 일이 있다고 힌트를 주셨는데, eventManager.js 구현에 필요하다는 것을 알게 되었습니다. - 처음 접하는
Weakmap에 대해 먼저 공부하고, 일반 Object와 map, 그리고 weakmap의 차이점을 먼저 습득하고 eventManager.js 구현에 나섰습니다. - 그럼, 이벤트 데이터를 어떻게 구조화하면 효율적으로 저장하고 꺼낼 수 있을까 고민하게 되었고, 문득 준일 코치님께서 이벤트 위임을 사용할 경우, 보통 최상위 엘리먼트(root container) 하나에 이벤트 리스너를 등록하고, 내부의 자식 요소들에서 발생하는 이벤트 버블링을 통하여 감지하고 처리한다라는 말씀을 하신 게 생각이 났고,
- 이를 바탕으로
eventType(click, blur, change...)을 키로 삼고,{ element, handler }형태로 구성해 보려고 했지만,Weakmap은 반복문 사용이 불가능하다는 점을 알게 되어,map으로 대체하게 되었습니다. - 최종적으로
Map을 기반으로 한eventStore를 만들었고, 각 eventType에 대해 내부적으로 또 다른 Map을 사용해 element와 handler를 저장했습니다. - 이벤트 핸들러는
Set을 사용해 중복 등록을 방지했습니다.
export function addEvent(element, eventType, handler) {
if (!eventStore.has(eventType)) {
// 스토어에 이벤트타입이 없으면 set
// 내부 맵은 어떤 element에 어떤 handler가 등록되어 있는지를 저장
eventStore.set(eventType, new Map());
}
// eventStore에서 이벤트 타입에 맞는 값을 뽑아온 후
const elementMap = eventStore.get(eventType);
// 뽑아오려고 했는데 없으면 set
if (!elementMap.has(element)) {
// 핸들러의 중복 방지를 위해 set
elementMap.set(element, new Set());
}
elementMap.get(element).add(handler);
}
export function removeEvent(element, eventType, handler) {
const elementMap = eventStore.get(eventType); // evenType
if (!elementMap) return;
const handlers = elementMap.get(element); // element에 맞는 handler들이 추출됨
if (!handlers) return;
// 인자로 받은 핸들러를 삭제
handlers.delete(handler);
if (handlers.size === 0) elementMap.delete(element); // 핸들러가 아예 없으면 (element에 등록된 이벤트가 아무것도 없으면) elementMap(eventStore)에서 삭제
if (elementMap.size === 0) eventStore.delete(eventType); // 이벤트 타입에 등록된 element가 아예 없으면 이벤트 타입 자체를 스토어에서 삭제
}
코드 품질
- 특히 만족스러운 구현
const newNodeChildren = Array.isArray(newNode.children) ? newNode.children : [];
const oldNodeChildren = Array.isArray(oldNode.children) ? oldNode.children : [];
const max = Math.max(newNodeChildren.length, oldNodeChildren.length);
for (let i = 0; i < max; i++) {
updateElement(targetElement, newNodeChildren[i], oldNodeChildren[i], i);
}
newNodeChildren을 기준으로 비교하자니 삭제된 요소를 감지하지 못하고,oldNodeChildren기준으로 비교하자니 추가된 요소를 감지하지 못해서 둘 중 length가 더 긴 기준으로 비교하려고Math.max()를 사용했습니다.- 리팩토링이 필요한 부분 + 코드 설계 관련 고민과 결정
createElement.. 이대로 괜찮은 건가 싶습니다. if문 떡칠입니다. 한 함수에 3개 이상 if문 들어가는 거 안 좋아하는데, 여러 타입에 대응해야 하다 보니if문이 많이 쓰였습니다. 클린 코드 책을 미리 읽었으면 이런 참사도 없었겠지요..
for (const [key, value] of Object.entries(props)) {
if (key === "children") continue;
if (key.startsWith("on") && typeof value === "function") {
// 이벤트 처리 - 위임 방식으로 처리하므로 DOM 속성으로 설정하지 않음
const eventType = key.slice(2).toLowerCase();
addEvent($el, eventType, value);
continue;
}
if (key === "style") {
Object.assign($el.style, value);
}
if (key === "className") {
$el.setAttribute("class", value);
continue;
}
if (booleanAttr.includes(key)) {
$el[key] = !!value;
continue;
}
학습 효과 분석
- DOM에서
node는 브라우저가 실제로 화면에 렌더링하는 요소의 최소 단위라는 것을 명확히 이해했습니다. HTML 요소, 텍스트, 주석 등 모든 DOM 구성 요소가Node객체로 표현된다는 점이 재미있었다. - React.js에서는 어떻게 필요한 부분만 쏙 골라서 새로운 DOM으로 변경하는 것인지 이번 과제를 통해 배웠습니다. vNode(Virtual DOM Node)를 먼저 만들고, 그것을 원본과 비교하여 변화가 생긴 부분만 실제 DOM에 반영하는 방식으로 성능을 최적화한다는 점을 배웠습니다.
- vNode를 비교해 실제 DOM과 비교해 반영하는 과정(diff 알고리즘 등)은 아직 낯설어서 추가 학습이 필요할 것 같습니다.
과제 피드백
- 이번 기회가 아니었으면 리액트가 어떤 식으로 SPA를 구성했는지, 왜 리액트에서는 기존 자바스크립트에서 무엇을 해결하고자 했는지 배웠습니다.
- 기본 과제에는
renderElement.js항목만 테스트에 있어서updateElement.js는 구현 안 해도 기본 과제는 통과하는 건가? 했는데...... 마지막 두 개의 테스트에서 막혔습니다. 그리고 e2e 테스트도updateElement.js가 정상적으로 구현되어야 pass가 되어서 그냥 다 구현했습니다. 😭
리뷰 받고 싶은 내용
eventManager.js에서 저는new Map()을 사용했지만,map이 아닌weakmap을 사용한다면 어떤 구조로 작성을 했어야 할까요?weakmap의 key는 오직 객체만 가능하고, 그 객체가 다른 곳에서 더 이상 참조되지 않으면 자동으로 가비지 컬렉션의 대상이 된다고 하지만, 반복문을 돌리지 못하는 것 때문에 구현에 어려움을 겪어map을 사용했지만 다른 분들은weakmap을 사용했다는 분들도 계셔서요. 코치님 같은 경우에는, 어떤 방식으로eventManager.js를 구현하셨을지 궁금합니다.
과제 피드백
안녕하세요 하늘~, 챕터 1-2 과제 정말 수고 많았습니다.
이번 과제를 통해 Virtual DOM의 작동 원리를 직접 구현하면서 React와 같은 프레임워크가 내부적으로 어떻게 효율적인 렌더링을 수행하는지 체득하는 경험을 하셨고, 특히 Map vs WeakMap 같은 자료구조등을 선택하고 고민하는 과정들이 잘 느껴졌어요.
이번 과제는 "알고 있는 것"과 "만들기 위해 필요한 것을 학습하는 것"의 차이를 몸소 체험하는 것이 핵심이었는데, 회고에서 작성하신 것처럼 처음 접한 WeakMap을 공부하고 eventManager 구현에 적용하려 고민하신 과정이 바로 그런 목적형 학습의 좋은 예시라고 생각합니다. 면접에서도 늘 왜 이 기술을 선택했나요? 혹은 왜 이렇게 만들었나요? 라는 질문의 맥락도 여기에서 기인하는 거죠. 만들기 위해서 하는 공부에서 정말로 많은 것들을 배울 수 있으니까요.
특히 이벤트 위임 시스템을 Map/Set을 활용해 계층적으로 설계하신 점이 인상적이었습니다. eventType을 키로, 그 안에 다시 element와 handler를 매핑하는 구조는 깔끔하고 직관적입니다.
Math.max를 사용해 자식 요소 비교 시 추가/삭제를 모두 감지하도록 구현하신 것도 훌륭한 접근이었습니다. 이런 세심한 고민들이 Virtual DOM의 원리를 제대로 이해하는 과정입니다.
구체적인 코드상의 부분들은 리뷰에서 거의 다 짚어줬네요. style 조건문에서 continue 누락이나 cleanChildren 2번 호출등이요. 좋은 팀이네요! AI도 좋은 친구이지만 실제로 이렇게 코드 리뷰를 함께 하면서 같이 성장할 수 있을 거라 생각합니다. 멋지네요.
수고 많았습니다. 다음 주차도 화이팅입니다!