과제 체크포인트
배포 링크
https://suhyeon57.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
1주차 과제는 전체적인 흐름이나 구조를 파악하지 않고, 무작정 코드부터 작성하면서 시작했습니다. 그 결과 전체 구조를 이해하지 못한 채 헤매는 시간이 길었고, 구현하는 데도 많은 어려움을 겪었습니다. 2주차는 가상 돔에 대한 개념부터 먼저 공부하고, 관련 문서와 자료를 찾아보며 전반적인 동작 방식에 대해 이해하고 나서 시작했습니다. 또한 발제 자료를 기반으로 뼈대를 먼저 세운 후, 테스트 코드를 중심으로 기능을 맞춰나가는 방식으로 접근하니, 1주차보다 훨씬 수월하게 진행할 수 있었습니다.
기술적 성장
children: children.flat( ), --> 1단계 평탄화만 이루어진다. children: children.flat(Infinity), --> 모든 깊이의 중첩 배열이 평탄화가 이루어진다.
Virtual DOM diff : 노드를 불필요하게 새로 만들지 않고, 변경된 부분만 실제 DOM에 반영
//항상 전체 DOM을 새로 생성 후 기존 내용을 모두 지우고 새로 추가 container.innerHTML = ""; // 기존 내용을 비우고 container.appendChild(dom); // 새로 생성한 DOM을 추가
<가상돔에 대한 전체적인 흐름 이해>
먼저, createVNode를 통해 가상 DOM을 생성하고, 컴포넌트 등 다양한 데이터 타입을 처리하기 위해 normalizeVNode를 통해 구조를 일관되게 정리한다. 그 후 정리된 VNode 트리를 기반으로 createElement를 사용하여 실제 DOM을 생성하고 화면에 초기 렌더링을 한다.
(createElement는 최기 렌더링 때 한 번 사용한다.)
상태가 변하거나 이벤트가 발생하면, createVNode를 이용해 새로운 VNode를 생성하고, 이전 VNode와 비교하는 updateElement를 통해 변경된 사항을 DOM에 반영한다. updateElement에서 updateAttributes를 통해 속성을 처리하고, 다시 자식 노드를 updateElement로 비교하여 갱신한다.
- addEvent : eventMap에 내부적으로 event를 추가하고 실제 돔에는 영향이 가지 않는다.
- addEventListener : setupEventListners를 통해 이벤트가 발생했을 때 eventMap에 저장된 이벤트가 실행된다.
gpt가 알려준 이벤트 위임의 장점
- 메모리 절약
각각의 자식 요소마다 이벤트 리스너를 따로 붙이지 않고, 상위 요소(root)에 한 번만 이벤트 리스너를 붙이므로 메모리 사용이 적어요.
- 동적 요소 지원 용이
동적으로 생성되거나 삭제되는 요소들도 별도의 이벤트 리스너 등록 없이 부모의 이벤트 리스너가 자동으로 처리해줘서 관리가 편해요.
- 코드 간결성 및 유지보수성 향상
이벤트 핸들러가 한 곳에 집중되어 있어 디버깅과 관리가 쉬워요.
- 성능 향상
이벤트 핸들러를 여러 개 붙이는 것보다 한 번만 붙여서 처리하는 게 성능에 더 유리해요.
코드 품질
이번 과제를 하며, AI가 주는 코드를 단순히 복사하지 않고 왜 필요한지, 어떤 흐름에서 쓰이는지 스스로 고민한 뒤 적용하려 노력했습니다. 그 과정에서 이해도도 높아졌습니다.
학습 효과 분석
가장 큰 배움은 createElement와 이벤트 처리 부분이었습니다. 초기엔 addEventListener를 잘못 사용해 이벤트 중복 문제가 발생했는데, 이를 해결하며 addEvent와 이벤트 위임 방식의 차이를 명확히 이해하게 되었습니다.
또한, 준일 코치님의 발제 자료는 처음엔 잘 이해가 되지 않았지만 직접 구현을 해본 뒤 다시 읽으니 훨씬 더 잘 와닿았습니다. 이 점도 인상 깊었습니다ㅎㅎ
과제 피드백
2주차 과제는 가상돔의 설계 이유와 동작 방식을 이해할 수 있는 좋은 기회였습니다. 특히 코드에 대한 이해를 바탕으로 리팩토링을 할 수 있는 시간이 있었다는 점이 좋았습니다. 1주차가 "완성만을 위한 구현"이었다면, 2주차는 공부하며 작성한 코드라는 점에서 개인적으로 의미 있었습니다.
3주차는 어떻게 진행될지 걱정이 되지만,, 열심히 해보겠습니다 😂
리뷰 받고 싶은 내용
const DELEGATED_EVENTS = ["click", "mouseover", "focus", "keydown", "change"];
export function setupEventListeners(root) {
if (root.__eventDelegationSetup) return;
root.__eventDelegationSetup = true;
DELEGATED_EVENTS.forEach((eventType) => {
root.addEventListener(eventType, (e) => {
// stopPropagation이 호출되면 위임 핸들러도 멈춰야 함
let node = e.target;
while (node && node !== root && !e.cancelBubble) {
const handlers = eventMap.get(node);
if (handlers && handlers[eventType]) {
handlers[eventType].forEach((fn) => fn.call(node, e));
}
node = node.parentNode;
}
}); // focus 등은 캡처 단계 필요
});
}
setupEventListeners 함수에서 DELEGATED_EVENTS 배열에 change를 빠뜨리는 바람에 UI 변화가 제대로 반영되지 않는 문제가 있었습니다. 현재는 이벤트를 배열에 하나하나 명시해줘야 하는데, 이런 방식만 가능한 건지 궁금합니다. 혹시 모든 DOM 이벤트를 자동으로 처리하게 할 수는 없을까요? 그렇게 작성하는 건 성능이나 안정성 면에서 괜찮을지도 궁금합니다.
과제 피드백
안녕하세요 수현~, 수고하셨습니다! 이번 과제는 React의 핵심 원리인 가상 DOM과 diff 알고리즘을 직접 구현해보면서, 프레임워크가 어떻게 효율적인 렌더링을 수행하는지 이해하는 것이 목표였습니다. '필요가 공부를 만든다'는 경험을 하셨기를 바랍니다. 단순히 "가상 DOM이란 무엇인가"를 읽고 아는 것과, "가상 DOM을 구현하기 위해 무엇이 필요한가"를 고민하며 공부하는 것은 다른 깊이를 요구한다는 것을 깨닫으면서 실무에 필요하지 않더라도 앞으로 깊이단계에 있는 것들을 스스로 만들어보면서 공부를 해보시기를 바래요.
회고에서 1주차의 시행착오를 바탕으로 2주차에는 체계적으로 접근하셨다는 점 아주 좋습니다. 가상 DOM의 전체 흐름(createVNode → normalizeVNode → createElement → updateElement)을 명확히 이해하고 구현하신 점, 그리고 이벤트 위임의 장점을 잘 파악하고 WeakMap으로 메모리 관리까지 고려하신 점 좋습니다 ^^ 지금과 같이 기록하면서 어떻게 성장했는지 스스로 느끼는 것이 실제 성취에도 큰 도움을 줍니다.
updateAttributes 함수의 else if가 많이 반복되는데 else if 대신 return이 가능하면 early return 패턴으로 작성하면 보다 가독성 높고 관리하기 좋은 코드가 된답니다.
이렇게 직접 프레임워크의 핵심을 구현해보니, 기존에 막연히 알고 있던 Virtual DOM의 개념이 훨씬 선명해졌을 거라 생각합니다. 이제 React를 사용할 때도 내부에서 어떤 일이 일어나는지 더 잘 이해하실 수 있을 거예요.
Q) 모든 DOM 이벤트를 자동으로 처리하게 할 수는 없을까요?
=> 좋은 질문입니다! 기술적으로는 가능하겠지만 염려한대로 모든 이벤트를 처음부터 다 등록해두는 것은 성능상 좋지 않겠죠. DOM에서 사용하는 이벤트의 종류는 매무 많으니까요. 사용하지 않을 메모리를 미리 사용하는 건 낭비입니다.
보통 이럴때는 대부분 필요할 때 필요한만큼 생성하는 동적 방식으로 구현을 하곤합니다. 지현도 생각했던 것처럼 addEvent가 호출될 때 해당 이벤트 타입을 Set에 저장하고, 처음 등록되는 이벤트 타입이라면 그때 root에 리스너를 추가하는 방식이죠. 이 방식도 꼭 한번 구현해보길 바래요.
수고하셨습니다. 3주차도 화이팅입니다! :)