과제 체크포인트
배포 링크
https://hyunzsu.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
-
JSX Transform 구현: Classic Transform 방식과 createVNode 함수로 React jsx-runtime.js 역할을 직접 구현
- JSX Transform 방식 비교 학습: Classic Transform(React 17 이전)과 Automatic Transform(React 17 이후) 차이점 이해
- 빌드 타임 vs 런타임 처리: esbuild가 JSX를 JavaScript로 변환하는 과정과 런타임에서 createVNode가 실행되는 과정 구분
-
가상 DOM 구현: React 내부 동작 원리를 직접 구현해보며 개념 이해
- 6단계 정규화 프로세스: normalizeVNode의 null/boolean → 원시타입 → 배열 → 객체 → 함수컴포넌트 → HTML엘리먼트 처리 과정
- 타입 안전성 구현: 다양한 vNode 타입(문자열, 숫자, 배열, 객체, 함수)을 안전하게 처리하는 메커니즘
-
이벤트 위임 시스템: eventManager를 통한 동적 요소들의 효율적인 이벤트 처리 메커니즘 구현
- 3단계 저장소 구조: element → eventType → handlers Set 구조로 이벤트 관리
- 버블링 탐색 알고리즘: event.target부터 루트까지 올라가며 핸들러를 찾는 시스템
-
DOM 업데이트 최적화: updateElement 함수로 가상 DOM 비교 및 DOM 패칭 알고리즘 구현
- 6단계 diff 알고리즘: 노드 추가 → 제거 → 텍스트 → 배열 → 타입변경 → 속성업데이트 순서로 처리
- DocumentFragment 활용: 여러 DOM 요소를 한 번에 조작하여 성능 최적화
코드 품질
updateAttributes 함수 성능 개선
-
변경사항: (updateElement.js)
-
문제점 기존 updateAttributes 함수는 속성 처리가 2단계로 나뉘어져 불필요한 중복 순회가 발생했습니다. 또한 이벤트 핸들러, className, Boolean 속성 처리 로직이 제거 단계와 추가 단계에서 각각 중복 구현되어 있어 특정 속성 처리 방식을 수정할 때 여러 곳을 동시에 고쳐야 하는 문제가 있었습니다.
-
개선 의도 이러한 문제들을 해결하기 위해 함수를 역할별로 분리했습니다. updateAttributes는 전체적인 흐름만 관리하고, 실제 속성 제거는 removeAttribute가, 설정은 setAttribute가 각각 담당하도록 구조를 개선했습니다.
// 개선 후: 메인 함수 - 전체 플로우만 관리
function updateAttributes(target, newProps, oldProps) {
const oldKeys = Object.keys(oldProps || {});
const newKeys = Object.keys(newProps || {});
// 1. 제거 단계
oldKeys.forEach((key) => {
if (!(key in (newProps || {}))) {
removeAttribute(target, key, oldProps[key]);
}
});
// 2. 추가/변경 단계
newKeys.forEach((key) => {
const oldValue = oldProps?.[key];
const newValue = newProps[key];
if (oldValue === newValue) return; // 조기 반환으로 성능 최적화
setAttribute(target, key, newValue, oldValue);
});
}
/**
* 속성 제거 전용 헬퍼 함수
* @param {HTMLElement} target - 대상 DOM element
* @param {string} key - 제거할 속성 키
* @param {*} oldValue - 제거할 속성의 이전 값
*/
function removeAttribute(target, key, oldValue) {
// ... 제거 로직 구현
}
/**
* 속성 설정 전용 헬퍼 함수
* @param {HTMLElement} target - 대상 DOM element
* @param {string} key - 설정할 속성 키
* @param {*} newValue - 설정할 새 값
* @param {*} oldValue - 이전 값 (이벤트 핸들러 교체 시 필요)
*/
function setAttribute(target, key, newValue, oldValue) {
// ... 설정 로직 구현
}
학습 효과 분석 (상세 문서화 작성하며 학습한 핵심 개념들)
- https://github.com/hyunzsu/front_6th_chapter1-2/issues/1: JSX를 Virtual DOM 객체로 변환
- https://github.com/hyunzsu/front_6th_chapter1-2/issues/2: 다양한 타입을 일관된 Virtual DOM 구조로 정규화
- https://github.com/hyunzsu/front_6th_chapter1-2/issues/3: Virtual DOM을 실제 DOM으로 변환
- eventManager.js: 이벤트 위임 시스템
- renderElement.js: Virtual DOM 렌더링의 진입점 - 전체 렌더링 프로세스 조율
- updateElement.js: Virtual DOM diff 알고리즘 구현
리뷰 받고 싶은 내용
1. Virtual DOM diff 알고리즘의 성능과 개선 방향
현재 updateElement 함수는 인덱스 기반 비교로 구현되어 있어, 리스트 순서 변경 시 성능 이슈가 있을 것 같습니다.
// 현재 구현 방식의 문제점
// Before: <li>Apple</li><li>Banana</li><li>Cherry</li>
// After: <li>Banana</li><li>Apple</li><li>Cherry</li>
// → Apple과 Banana의 텍스트 내용을 모두 교체 (비효율적)
// 이상적인 방식: key 기반 비교로 DOM 요소 이동만 수행
질문:
- 현재 과제 수준에서 키 기반 diff 알고리즘 구현이 필요한지, 아니면 인덱스 기반으로도 충분한지 궁금합니다.
- 만약 키 기반 구현이 필요하다면, 어떤 우선순위로 접근하는 것이 좋을까요?
2. updateAttributes 함수 리팩토링의 설계 방향성
성능 개선을 위해 updateAttributes 함수를 헬퍼 함수로 분리했습니다:
// 개선 후: 메인 함수(30줄) + removeAttribute(30줄) + setAttribute(35줄)
질문:
- 이런 함수 분리 방식이 적절한지 궁금해요!
3. 이벤트 위임 시스템의 확장성
현재 구현한 이벤트 위임 시스템에서 성능과 메모리 효율성에 대해 궁금합니다:
// 현재 구현: 3단계 저장소 구조
eventMap = Map {
element => Map {
eventType => Set { handlers }
}
}
질문:
- 대규모 애플리케이션에서 eventMap이 메모리 누수의 원인이 될 가능성은 없을까요?
- WeakMap 사용을 고려해야 하는 상황이 있을까요?
4. Boolean 속성 처리의 브라우저 호환성
updateAttributes에서 Boolean 속성을 처리할 때 checked/selected는 Property만, readOnly는 Attribute+Property, 일반 Boolean은 둘 다 설정하는 방식으로 구현했는데:
if (key === 'checked' || key === 'selected') {
$el[key] = value; // Property만
} else if (key === 'readOnly') {
// Attribute + Property 둘 다
} else {
// 일반 Boolean: Attribute + Property 둘 다
}
질문:
- 이런 케이스별 다른 처리 방식이 모든 브라우저에서 안전한지 궁금합니다.
- 더 일관성 있는 처리 방법이 있을까요?
과제 피드백
안녕하세요 지수님! 2주차 과제 잘 진행해주셨네요 ㅎㅎ 현재 PR에 특별한 질문이 없는 상태, 궁금한게 있으면 언제든 문의채널에 남겨주세요!! 고생하셨습니다!
세상에 ㅋㅋㅋ 정리를 너무 잘 해주셨네요. 이대로 미리 제줄해주셨으면 BP 예약이었을텐데 ㅠㅠ 아쉬워요
현재 과제 수준에서 키 기반 diff 알고리즘 구현이 필요한지, 아니면 인덱스 기반으로도 충분한지 궁금합니다.
현재 과제의 목표는 리액트의 렌더링 시스템을 맛보는 것이었어요 ㅎㅎ 그래서 지금 수준으로도 충분하다고 생각합니다.
만약 키 기반 구현이 필요하다면, 어떤 우선순위로 접근하는 것이 좋을까요?
children에 대해 updateElement를 수행할 때, children의 key를 토대로 위치를 탐색해서 리렌더링에 대한 결정을 해야하지 않을까 싶네요!
다만 구현해봐야 알 것 같아요 ㅋㅋ 제 스스로가 구현하기 전에는 잘 모르는 게 많아서.. ㅠㅠ.. 확신은 없네요.
성능 개선을 위해 updateAttributes 함수를 헬퍼 함수로 분리했습니다. 이런 함수 분리 방식이 적절한지 궁금해요!
충분히 잘 해주셨다고 생각해요! 다만 실제로 얼마나 성능 개선이 되었는지는 측정을 해봐야 알 것 같네요!
대규모 애플리케이션에서 eventMap이 메모리 누수의 원인이 될 가능성은 없을까요? WeakMap 사용을 고려해야 하는 상황이 있을까요?
map으로만 관리하는 경우, map에 등록된 key가 의도하지 않게 사라졌을 때 어떻게 해야 좋을지 고민해보시면 좋답니다 ㅎㅎ 이를 위해서 weakmap이 있는거라서요.
정상적인 로직만 수행하면 문제될게 없겠으나... 우리가 설계한 것과 다르게 돔을 조작하는 경우, 가령 setState 같은 함수가 아니라 직접적으로 돔을 없애는 경우에는 돔에 대한 이벤트 트리거가 제거되지 않을수도 있답니다!
Boolean 속성 처리의 브라우저 호환성
제일 좋은건 다룰 수 있는 모든 property에 대해 정의해서 사용하는거라고 생각해요. 창준님께서 남겨주신 리뷰가 제 생각과 맞닿아있네요!
모든 브라우저에서 안전한지는 돌려봐야 알 것 같아요 ㅋㅋ 처음부터 모든 이슈를 찾아내면 좋겠지만, 현실적으로 불가능에 가까워서 문제가 있을 때 빠르게 찾아낼 수 있는 장치를 만드는게 좋다고 생각해요.
"이게 모든 브라우저에 호환이 될까?"가 아니라 "호환이 안 되면 어떻게 찾아내야 좋을까?" 로 접근하는게 좋다고 생각합니다.