과제 체크포인트
배포 링크
https://angielxx.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
1. createVNode — /** @jsx createVNode */의 역할
과제의 첫 번째 항목인 createVNode를 구현하면서, 이 함수가 어디서 호출되는지 몰라 한참 헤맸습니다. createVNode를 검색해보니 파일 최상단에 다음과 같이 정의된 것을 발견했고, 이 주석의 의미를 먼저 학습했습니다.
/** @jsx createVNode */
import { createVNode } from "../lib";
👉 JSX pragma
- JSX 문법은 Babel이나 TypeScript에 의해 함수 호출 코드로 변환됩니다.
- 기본적으로는 React.createElement를 호출하지만, 위의 주석을 작성하면 이를 createVNode로 변경합니다.
// 변환 전
<div className="foo">bar</div>
// 변환 후
createVNode("div", { className: "foo" }, "bar");
JSX가 createVNode를 호출한다는 점을 이해한 뒤, 반환값이 { type, props, children } 형태가 되도록 구현했습니다. 이 과정을 통해 JSX 변환 과정과 JSX pragma의 존재 이유를 명확히 알게 되었습니다.
2. normalizeVNode — VNode 정규화
normalizeVNode는 테스트 코드에만 등장하고 코드 상 어디서 호출되는지 보이지 않아 역할을 파악하기 어려웠습니다. 이름에서 유추하듯 VNode를 일관된 형식으로 표준화하는 역할이라고 판단하고 구현을 시작했습니다.
👉 문제와 해결
- 중첩된 함수형 컴포넌트에서 정규화가 제대로 되지 않아 테스트가 통과하지 않았습니다.
- 작은 단위의 컴포넌트를 만들어 테스트해 보니, 함수 컴포넌트를 자식으로 넘겼을 때 children이 비어 있음을 발견했습니다.
- 함수형 컴포넌트가 반환하는 VNode 또한 재귀적으로 정규화해야 한다는 것을 깨닫고 수정했습니다.
이 과정에서 자식 노드도 함수일 수 있다는 점과, 재귀 호출의 필요성을 깊이 이해했습니다.
3. createElement - DOM 다시 공부하기
createElement는 실제 DOM 요소를 만드는 함수이기 때문에, MDN 문서를 통해 DOM API에 대해 다시 학습했습니다. 이 과정에서 DOM 개념에 대해 잘못 알고 있었다는 사실을 인지했습니다.
📖 DOM이란?
DOM ≠ HTML
- DOM은 HTML을 프로그래밍으로 다루기 위한 문서 객체 모델
- 문서를 트리 형태의 노드와 객체로 표현하여, JavaScript로 접근해 문서 구조와 내용을 변경
📖 DocumentFragment를 사용하는 이유
- DocumentFragment는 부모가 없는 임시 문서 객체
- DOM에 추가되기 전까지는 리플로우(reflow)를 발생시키지 않고, 가볍게 여러 요소를 조립
- 한 번에 DOM에 붙이기 때문에 나은 성능 제공
4. eventManager — 이벤트 위임의 이해
이벤트를 처리하는 과정에서, 동적 요소에 이벤트가 여러 번 중복 등록되거나 일부가 누락되는 문제가 있었습니다. 과제를 시작하기 전 학습자료를 모두 정독하긴 했지만,,,이벤트 위임의 개념을 명확히 이해하지 못한 상태로 코드를 작성하고 있다는 것을 깨달았습니다. IDE는 잠시 접어두고 이벤트 위임에 대한 학습을 다시 진행했습니다.
👉 이벤트 위임이란?
여러 자식 요소에 각각 리스너를 붙이는 대신, 부모 요소에 하나의 리스너를 붙이고 이벤트 버블링을 이용해 처리하는 방법.
👉 이벤트 위임 사용 이유
- 메모리 절약: 부모에 하나만 붙이면 되므로 메모리를 적게 사용
- 동적 요소 대응: 나중에 추가된 요소도 부모가 감지
- 코드 간결: 반복문 없이 부모에만 리스너를 작성
// 각각 등록
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', () => {
console.log('clicked', item.textContent);
});
});
// 이벤트 위임
const list = document.querySelector('.list');
list.addEventListener('click', (event) => {
if (event.target.matches('.item')) {
console.log('clicked', event.target.textContent);
}
});
5. renderElement — DOM 재사용
renderElement를 처음 구현했을 때는 container.innerHTML = ""로 기존 DOM을 모두 지우고 새로 만드는 방식이었습니다. 하지만 이렇게 하면 기존 DOM의 참조가 유지되지 않기 때문에, 성능이 떨어지고 이벤트 핸들러도 초기화됩니다.
👉 개선
- 최초 렌더링 시에는 createElement 사용
- 이후 렌더링 시에는 updateElement를 사용하여 필요한 부분만 갱신하도록 수정
if (!previousVNode) {
const element = createElement(normalizedVNode);
container.appendChild(element);
} else {
// 이후 렌더링: 모든 자식 요소를 updateElement로 업데이트
updateElement(container, normalizedVNode, previousVNode, 0);
}
코드 품질
“동작만 하게 만드는 것”이 아니라, “올바른 설계와 유지보수가 가능한 코드”를 작성하는 것이 중요하다는 점을 크게 느꼈습니다.
- 초기에 설계와 개념을 충분히 이해하지 못해 잘못된 방향으로 구현했던 점이 아쉬웠습니다.
- 함수의 책임을 분리하고, 테스트하기 쉽게 만드는 습관의 필요성을 느꼈습니다.
학습 효과 분석
🌟 새롭게 배운 것
- JSX pragma
- 이벤트 위임 (버블링과 메모리 절약, 동적 요소 처리)
🔄 복습한 것
- DOM의 정의와 구조
- DOM과 JavaScript의 관계
📈 성장한 점
- 두 번의 과제를 통해 스스로 가지고 있는 나쁜 습관을 인지: 몸으로 부딪히고 문제가 발생하면 그 때 처음으로 돌아가는 습관을 발견했습니다.
- 디버깅할 때는 문제를 잘게 쪼개어 작은 단위로 테스트하는 것이 효과적이라는 걸 경험했습니다.
- 과거에 공부했던 내용을 다시 되새기고, 제대로 이해하지 못한 부분을 보완할 수 있었습니다.
과제 피드백
리뷰 받고 싶은 내용
코멘트로 혼자 의문이 가는 부분에 대해 남겨놓았는데, 코멘트를 검토해주시면 감사하겠습니다!
과제 피드백
안녕하세요 은지, 수고하셨습니다. 이번 과제는 React의 핵심 원리인 가상 DOM과 diff 알고리즘을 직접 구현해보면서, 프레임워크가 어떻게 효율적인 렌더링을 수행하는지 이해하는 것이 목표였습니다. 특히 이번 과제를 통해 '필요가 공부를 만든다'는 경험을 하셨기를 바랍니다. 단순히 "가상 DOM이란 무엇인가"를 읽고 아는 것과, "가상 DOM을 구현하기 위해 무엇이 필요한가"를 고민하며 공부하는 것은 완전히 다른 깊이의 학습이니까요.
그런 점에서 회고에서 JSX pragma의 역할을 몰라 한참 헤맸다고 하신 부분이 참 인상적이었어요. createVNode이 어디서 호출되는지 찾아가며 / @jsx createVNode */ 주석의 의미를 학습하신 과정에서 내가 기존의 학습방식과 필요하니까 찾아가면서 하는 목적형 체험 학습의 경험을 꼭 기억해주기를 바랍니다. 그밖에 이벤트 위임이니 WeakMap등에 대한 검토들도 너무 좋았습니다.
회고에서 "몸으로 부딪히고 문제가 발생하면 그때 처음으로 돌아가는 습관"을 발견하셨다고 하셨는데, 사실 이것도 나쁘지만은 않습니다. 그 몸으로 부딫히는 시행착오로 인해서 그 길이 아니었구나라는 경험이 있기에 다음번에서는 그길로 가지 않게 되니까요. 헤메는 만큼 다 습득을 하는 것이니까요.
그렇지만 깨달음을 얻었던 것처럼 하나씩 해야할 Task들을 적절히 단계별로 만들어두고 가는것은 개발자의 구조적 사고 능력중 중요한 능력이므로 같이 연습해보면 좋겠네요.
수고많았습니다. 다음 주차도 화이팅입니다!