- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
- 비사용자는 포스트 작성 폼이 보이지 않는다
- 비사용자는 포스트에 좋아요를 클릭할 경우, 경고 메세지가 발생한다.
- 사용자는 포스트 작성 폼이 보인다.
- 사용자는 포스트를 추가할 수 있다.
- 사용자는 포스트에 좋아요를 클릭할 경우, 좋아요가 토글된다.
- vNode 형태의 가상 DOM을 만들어 children flat화
VNode: {
type: "div",
props: { id: "test" },
children: ["Hello"]
}
- vNode(가상 DOM) 노드를 정규화
- null, undefined, boolean 값은 빈 문자열로 변환
- 문자열이나 숫자는 문자열로 변환
- 자식 노드들이 있으면 재귀적으로 정규화
- 함수 타입의 컴포넌트는 실행시켜 반환된 결과를 다시 정규화
즉 다양한 형태의 가상 DOM 노드를 일관된 구조로 변환하여 렌더링 과정을 단순화한다.
vNode: {
type: [Function: ListItem],
props: { id: 'item-3', className: 'last-item' },
children: [ 'Item 3' ]
}
{
type: 'li',
props: { id: 'item-3', className: ... },
children: [ ... ]
}
- 가상 노드 DOM을 실제 DOM 요소로 변환 (다양한 타입의 vNode 처리)
- 기본 타입(null, undefined, boolean): 빈 텍스트 노드 반환
- 문자열/숫자: 텍스트 노드 반환
- 배열: 각 항목을 재귀적으로 처리해 DocumentFragment 생성
- 태그 객체: HTML 요소 생성 및 속성/자식 설정
- 함수 컴포넌트: 함수 실행 결과 반환
Ex. div, span -> HTMLDivElement, HTMLSpanElement
if(vNode === null || vNode === undefined || typeof vNode === "boolean") {
document.createTextNode("");
}
if(typeof vNode === "string" || typeof vNode === "number") {
document.createTextNode(vNode)
}
...
-
3-1. 생성된 DOM 요소에 속성 설정 (여러 특수 속성 처리)
- null/undefined: 속성 제거
- style: CSS 텍스트 적용
- className: class 속성 설정
- on으로 시작하는 속성: 이벤트 리스너 등록(아래 3-2)
- data-* 속성: 데이터 속성 설정
-
3-2. 생성된 DOM 요소에 이벤트 리스너 연결
- on으로 시작하는 이벤트 리스너 연결 (Ex. onClick, onChange, onMouseEnter, onMouseLeave ...)
- 이벤트 관리 시스템
- 4-1. 이벤트 위임 방식을 통해 루트 요소에 등록된 모든 이벤트 타입 & 리스너 설정
- 4-1-1. 실제 브라우저 DOM에는 모든 컴포넌트마다 이벤트를 등록하지 않고 document나 root div 하나에 리스너를 등록해두고 거기까지 버블링된 이벤트를 기반으로 어떤 컴포넌트에서 발생한 건지 찾아 처리해준다. ✨
- 4-1-2. 이는 메모리 효율성(수천개의 버튼이 있어도 이벤트 리스너는 단 한개), 동적으로 생성된 요소도 커버(리렌더링마다 리스너를 다시 붙이지 않아도된다), 컴포넌트 분리와 이벤트 처리의 분리로 유지보수성이 좋아진다.
- 4-2. 이벤트 핸들러 및 이벤트 타입과 관련된 모든 핸들러 제거
<button onClick={handleClick}>Button 1</button>
<button onClick={handleClick}>Button 2</button>
<button onClick={handleClick}>Button 3</button>
document.addEventListener('click', handleClick);
- 이벤트 관리 시스템을 구현하면서 이벤트 위임에 대해 더 깊게 이해하는 시간이 되었다고 생각합니다.
- 지난주 과제에서 이벤트를 자식 요소에 하나하나 등록하지 않고 상위 요소에 등록한 후 이벤트 전파를 이용하여 처리하는 정도로만 이해하고 사용했는데요.
- 이번 이벤트 관리 시스템을 구현하게 되면서 React가 이벤트 위임 & 이벤트 버블링 기반으로 구현되어있다는 사실을 알게 되었습니다.
- 또한 전파 방식으로 캡처링과 버블링 두 가지 방식이 있다는 사실도 알게 되었습니다.
버블링 순서 : 이벤트가 발생한 타깃에서부터 상위 요소들로 전파
button(children) -> root(parent) -> document(root)
캡처링 순서 : 루트에서부터 타깃으로 내려가면서 이벤트 전파
-
최초 렌더링(oldvNode가 없는 경우) 시에는 새 DOM 요소를 생성해 컨테이너에 추가
-
이후 렌더링에는 기존 DOM을 제거 후 diff 알고리즘 활용하여 새 가상 DOM에 맞게 업데이트
-
현재 가상 DOM을 컨테이너의 vNode 속성에 저장
-
렌더링이 완료되면 컨테이너에 이벤트 리스너 설정 (즉 개념 & 파일 작성은 1-4번, 렌더링에 따른 가상 돔 생성 & 변경은 5-8번 반복)
이번 프로젝트를 통해 가상 DOM의 렌더링 시스템과 이벤트 관리 시스템에 대한 깊은 이해를 얻었습니다.
특히 이벤트 위임 방식과 이벤트 전파(버블링)에 대해 개념을 공부하고 구현 및 실습해보면서 React의 이벤트 처리 메커니즘에 대해 알게 되었습니다.
-
가상 DOM의 정규화 및 변환 과정을 직접 구현하면서, 다양한 형태의 데이터와 컴포넌트를 일관된 구조로 처리하는 것을 알게 되었습니다.
-
이벤트 위임과 이벤트 리스너 관리를 구현하면서, 두 개념에 대해 명확하게 알 수 있었습니다.
- 여러 개의 컴포넌트가 있을 때, 각 컴포넌트에 개별적으로 이벤트를 등록하는 대신, 루트 요소에 한 번만 리스너를 등록했습니다.
-
가상 DOM의 생성과 업데이트 과정에서 정규화, DOM 변환, 속성 설정, 이벤트 처리 단계까지 순차적으로 이해하고 구현하며 렌더링 시스템의 작동 원리를 철저히 익혔습니다.
-
이벤트 버블링과 이벤트 캡처링의 차이점을 개념을 익히고 실제 코드들을 분석해보면서 이벤트 전파 방식을 명확히 이해했습니다.
-
특히 이벤트 위임 방식과 이벤트 리스너 관리를 활용해, React의 이벤트 처리 방식에 대한 깊은 통찰을 얻을 수 있었습니다.
-
이번 과제를 통해 가상 DOM과 이벤트 관리 시스템을 구현하면서, React와 같은 프레임워크에서 사용하는 핵심 개념들(이벤트 위임, 이벤트 전파, DOM 업데이트 등)을 실무에서 어떻게 적용할 수 있는지에 대해 깊이 이해하게 되었습니다.