과제 체크포인트
배포 링크
https://minjaeleee.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
- JSX의 트랜스파일링 과정 학습
- 막연하게 Babel과 같은 트랜스파일러 덕에 jsx를 적용할 수 있다는 개념만 알고 있었습니다. 이번 학습을 통해서 더 자세하고 정교하게 공부할 수 있었습니다. 또한, 가상 DOM이 jsx -> createVNode -> createElement -> renderElement ( + updateElement) 과정을 통해 실제 DOM에 적용되는 과정을 학습했습니다.
- esbuild.jsxFactory의 역할 (상세 보기)
- 처음에 이 JSX 파일들을 어디서부터 과정을 시작해야할지 몰라 막막했고, createVNode 함수가 왜 실행이 되어야 하는지 이해하지 못했습니다. esbuildjsxFactory의 역할을 통해서 결국엔 createVNode를 통해 jsx가 js로 변환하는 과정을 학습했습니다.
- createVNode (상세 보기)
- JSX를 가상 DOM 객체로 변환하는 역할에 대해서 학습했습니다. 특히 객체 내부의 type, props, children의 의미와 리턴 값들을 보면서 노드가 어떻게 구조화하는지 이해할 수 있었습니다. 직접 모든 노드 요소에 대해서 console.log()를 실행해가며 노드와 객체로 리턴된 값들을 비교하며 더 구체적으로 학습할 수 있었습니다.
- normalizeVNode (상세 보기)
- vNode를 일관된 형태로 정규화하기 위한 함수의 역할을 이해했습니다. 특히나, jsx를 작성하기만 하다보니 다양한 형태의 vNode에 대한 이해가 많이 부족했는데 불필요하게 생성되는 값들을 제거하고, 함수형 vNode들을 실행시키는 등을 직접 경험하면서 전처리의 핵심 역할을 담당한다는 것을 학습할 수 있었습니다.
- createElement(상세 보기)
- createVNode와 normalizeVNode를 기반으로 실제 DOM 노드를 생성하는 함수에 대해서 이해할 수 있었습니다. 특히나 DOM의 attributes 들을 직접 조작하고 렌더링하는 것이 이해하기 어려웠습니다. 책이나 문서로는 리액트의 등장 배경에 대해서 설명할 때, 선언형의 장점, 직접 DOM을 조작하지 않아서 생기는 장점, 상태와 UI의 분리적 구조에 대해서 외우고 있었는데 실제로 효율적으로 관리하는 경험을 할 수 있었습니다.
- renderElement(상세 보기)
- React의 ReactDOM.render 기능과 유사한 역할을 하는 renderElement 함수를 직접 작성해보며 가상 DOM을 실제 DOM에 렌더링하는 과정에 대해서 학습할 수 있었습니다.
코드 품질
학습 효과 분석
가상 DOM 렌더링 원리를 직접 구현함으로써 JSX transform, VNode 생성, 실제 DOM 생성 구조 등에 대해서 깊숙이 이해할 수 있었습니다. 개인적으로는 지난주 과제보다 이번주 과제가 더 어려웠습니다. 노드의 type과 props, children의 구조와 케이스를 모두 이해해야하며 attributes, element들을 모두 조작하고 렌더링시켜야 하기 때문에 많이 어려웠습니다. 초기 구현은 ai의 도움을 받았고 develop과 완성도는 테스트 코드를 수정하며 높였습니다… 기능 완성 -> 테스트 코드 수정 -> 다른 기능 수정 -> 기능 완성 -> 테스트 코드 수정의 반복이었습니다..
또한, 이벤트 위임 구조를 작성하며 root 기반 등록 방식 메모리 제어 구조를 배울 수 있었습니다.
과제 피드백
리뷰 받고 싶은 내용
- updateElement 함수에서 children 요소들을 아래와 같이 index로 구분하는 것이 비효율적일 것 같다는 생각이 드는데, 만약에 요소의 순서만 변경해도 모두 변경되었다고 감지하고 diff 업데이트를 할 것 같습니다. 더 효율적으로 개선할 수 있는 방법이 있을까요??
// 6. newNode와 oldNode의 모든 자식 태그를 순회하며 1 ~ 5의 내용을 반복한다.
const newChildren = newNode.children || [];
const oldChildren = oldNode.children || [];
// 먼저 공통된 자식들을 처리
for (let i = 0; i < Math.min(newChildren.length, oldChildren.length); i++) {
updateElement(parent.childNodes[index], newChildren[i], oldChildren[i], i);
}
- 루트 요소에 한 번만 이벤트 리스너를 등록하고, 요소별 핸들러는 addEvent로 Map에 저장하는 이벤트 위임 구조를 사용하고 있는데 예를 들어서 addEvent()를 호출하지 않고 DOM에만 append된 요소라든지, e.stopPropagation()이 중간에 호출되어서 루트까지 도달하지 못 하는 경우가 생길 수 있을 것 같은데 벤트 구조를 확장하면서도 안정적으로 관리할 수 있는 구조적/전략적 개선 방안을 조언받고 싶습니다.
과제 피드백
안녕하세요 민재, 수고하셨습니다 :) 이번 과제는 React의 핵심 원리인 가상 DOM과 diff 알고리즘을 직접 구현해보면서, 프레임워크가 어떻게 효율적인 렌더링을 수행하는지 이해하는 것이 목표였습니다. 특히 단순히 "가상 DOM이란 무엇인가"를 이론으로 공부하고 아는 것과 "가상 DOM을 구현하기 위해 무엇이 필요한가"를 고민하며 학습하는 것은 완전히 다른 깊이의 경험이라는 것을 체험하는 시간이 되었기를 바랍니다.
JSX → createVNode → normalizeVNode → createElement → updateElement의 전체 흐름을 체계적으로 구현하셨네요. 특히 DocumentFragment를 활용한 배열 처리와 이벤트 버블링/캡처링을 구분하여 처리한 점이 인상적입니다. 회고에서 "기능 완성 → 테스트 코드 수정"의 반복 과정을 겪으셨다고 하셨는데, 시행착오를 겪는 동안 오히려 각 API의 세밀한 동작들이 머리속에 잘 새겨졌을 거에요!
이벤트 핸들러 저장에 Map을 사용하고 있는데 이 부분은 WeakMap으로 변경하면 더 좋겠습니다. DOM 요소를 key로 사용하는 경우에는 DOM에 제거될 때 자동으로 이벤트 핸들러도 가비지 컬렉션되어 메모리 누수를 방지할 수 있거든요. 또한 updateAttributes에서 boolean 속성들을 하드코딩한 배열로 관리하는 것보다는 속성의 타입을 동적으로 체크하는 방식을 고려해보시면 확장성이 더 좋아질 것 같습니다.
이렇게 프레임워크의 내부 동작을 직접 구현해보면 단순한 이론이 아닌 실제 구조와 흐름을 체득하게 됩니다. 이제 React를 사용할 때 "왜 key가 필요한지", "왜 이벤트 위임을 사용하는지" 같은 설계 결정들이 더 명확하게 이해되실 거예요. 앞으로도 이런 저수준 구현 경험을 통해 더 깊이 있는 개발자로 성장하시길 바랍니다. 수고많았습니다.
Q) updateElement에서 index 기반 비교의 비효율성 개선 방법
=> 정확한 지적입니다! 현재 방식은 순서만 바뀌어도 모든 노드를 재생성하는 문제가 있죠. React에서 반복된 요소를 그릴때 반드시 key를 요구하는 것도 이 때문입니다. key 기반 매칭을 구현하려면 oldChildren을 Map으로 변환하여 key로 빠르게 찾을 수 있게 하고, 같은 key를 가진 노드끼리만 비교하도록 하고 있죠. 이번 과제에는 제외했지만 중요한 React의 핵심 로직이니 한번 시도해보세요!
Q) 이벤트 위임 구조의 안정성 개선 방안
=> 현재 구조도 잘 구현되어 있어요. 몇가지 개선 방안들을 공유하자면 우선 MutationObserver를 활용하면 동적으로 추가된 요소도 자동으로 감지할 수 있습니다. 그리고 이벤트 타입을 하드코딩하지 말고 실제 사용된 이벤트만 동적하는 방법도 생각해보세요.
수고하셨습니다. 다음 주차도 화이팅입니다! :)