과제 체크포인트
배포 링크
https://areumh.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
가상 돔에 대한 사전적 정의와 사용 이유에 대해서만 얕게 알고 있었는데, 이번 과제를 통해 가상 DOM과 실제 DOM의 동작 차이, 이벤트 위임과 버블링의 구체적인 동작 방식 등에 대해 더 깊이 알게 된 것 같다.
코드 품질
현재는 과제로 출제된 각 함수 파일 그대로 구현했는데, 추후에 리팩토링을 한다면 createElement, normalizeVNode 함수에서 조건문에 사용되는 vNode의 타입을 확인하는 함수를 따로 작성하고 싶다. 그리고 updateElement 함수 내에서 속성 업데이트에 사용되는 updateAttribute 함수가 가독성 면에서 조잡해보이는 느낌이 있어서 만약 리팩토링을 한다면 유틸 함수로 역할을 분리하거나 createElement의 updateAttribute에도 재사용할 수 있는 함수로 분리하여 진행해보고 싶다.
학습 효과 분석
- updateAttribute - boolean 타입 props의 직접 업데이트
updateElement에서 boolean 타입의 속성 값을 업데이트할 때 createElement에서와 같은 동작인 setAttribute(attr, "")으로 작성하여 헤맨 기억이 있다.
else if (typeof value === "boolean") {
if (attr in target) {
// target에 attr가 존재하는지 확인
target[attr] = value; // 있으면 직접 업데이트
if (!value) {
// attr가 존재하는데 값이 false
// 해당 attr 삭제
target.removeAttribute(attr);
}
} else {
// attr가 존재하지 않음
// true일 때 빈 문자열로 속성 추가
value && target.setAttribute(attr, "");
}
}
실제 DOM은 직접 속성을 업데이트해줘야 하기 때문에 target[attr] = value; 처럼 해당 속성이 존재할 경우엔 값을 직접 업데이트해주도록 하여 해결했다.
- 이벤트 버블링
// src/lib/eventManager.js (setupEventListeners())
const handleEvent = (event) => {
const target = event.target;
if (!handlerMap.has(target)) return;
const handler = handlerMap.get(target);
if (handler) handler(event);
};
처음 setupEventListener 함수를 작성했을 때, 이벤트 실행 시 해당 요소에 등록된 이벤트만을 검사하여 실행하도록 구현했는데 이 때문에 제일 기본적인 페이지 라우트 테스트를 통과하지 못해 많은 시간을 허비했다.
const handleEvent = (event) => {
let target = event.target;
// 클릭된 요소와 이벤트가 연결된 요소가 다름
while (target && target !== event.currentTarget) {
if (handlerMap.has(target)) {
// 등록된 이벤트 함수
const handler = handlerMap.get(target);
// 실행시킨 후 종료
if (handler) handler(event);
break;
}
// 부모 요소로 올라가며 반복
target = target.parentElement;
}
};
위의 코드처럼 현재 요소의 부모 요소를 하나씩 검사하면서 이벤트 함수가 있으면 해당 이벤트를 실행하도록 코드를 수정하여 해결했다.
과제 피드백
이번 과제를 통해 가상 돔을 다뤄보면서, 평소라면 깊게 고민하지 않았을 부분까지 깊이 있게 이해할 수 있었습니다. 만약 이번 과제처럼 구체적으로 다뤄보는 기회가 없었다면, 가상 돔이 어떻게 동작하는지, 왜 필요한지에 대해 학습할 일은 없었을 것 같아서 의미있었다고 생각한다!!
리뷰 받고 싶은 내용
setupEventListener 함수에서 이벤트 핸들러 저장, root에 연결된 이벤트 관리에 Map이 적절히 사용된건지 의견을 여쭤보고 싶습니다. (이벤트를 연결하는 데에 더 좋은 방안이 있는지.) 그리고 어떠한 요소에 대해 발생해야 하는 이벤트가 부모 요소에 연결된 이벤트일 경우 event.currentTarget을 사용하여 부모 요소를 차례로 확인하여 연결하도록 구현하였는데, 이 또한 적절한 방식인지 궁금합니다.
과제 피드백
안녕하세요 아름! 수고하셨습니다. :) 이번 과제는 가상돔의 원리를 이론이 아닌 직접 직접 구현해보며 렌더링 최적화와 이벤트 처리의 핵심을 깊이 있게 탐구하는 것이 이번 과제의 의도였어요. 이 과제를 통해 프레임워크의 동작 방식을 몸으로 체득하는 경험을 그리고 깊이를 공부하는 방법을 알게 되는 계기가 되었기를 바랍니다.
회고에서 적어준 이벤트 버블링 문제등에서 event.target만 확인하는 단순한 방식으로 구현하셨다가, 페이지 라우트 테스트 실패를 통해 문제를 발견하고 while문으로 부모 요소를 순회하며 핸들러를 찾는 로직으로 개선하는 등 이러한 접근 과정을 통해서 순회나 실제 이벤트의 동작원리등이 더 선명해졌으리라 생각합니다. 잘했어요.
updateElement에서 boolean 속성 처리 시 setAttribute(attr, "")가 아닌 target[attr] = value 방식으로 직접 업데이트해야 한다는 것을 발견하신 것도 DOM API의 세밀한 차이를 잘 파악하신 결과라 생각합니다.
코드 리뷰등을 통해서 알게된 느슨한 비교등으로 더 적은 횟수의 비교를 쓰게 하는 것들도 저수준 라이브러리에서 중요한 내용이죠. createVNode의 children 필터링도 filter(x => x || x === 0)로 단순화할 수도 있겠습니다.
"필요가 공부를 만든다"는 말처럼, 직접 구현해야 하는 상황에서 학습한 내용이 가장 오래 남는 법입니다. 이제 가상 DOM을 직접 만들어본 경험이 있으니, 앞으로 리액트나 뷰를 사용할 때도 내부에서 무슨 일이 일어나는지 더 잘 이해하실 수 있을 거예요. 다음 주차도 화이팅입니다! :)
질문하신 내용에 대해서는 아름이 생각했던 그 방식들 다 맞는 방식입니다. 잘했습니다.