과제 체크포인트
배포 링크
https://yuhyeon99.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
이슈로 정리한 각 기능별 개요, 구현 요약, 구현하면서 느낀 점
- yuhyeon99/front_6th_chapter1-2/issues/1
- yuhyeon99/front_6th_chapter1-2/issues/2
- yuhyeon99/front_6th_chapter1-2/issues/3
- yuhyeon99/front_6th_chapter1-2/issues/4
- yuhyeon99/front_6th_chapter1-2/issues/5
- yuhyeon99/front_6th_chapter1-2/issues/6
- yuhyeon99/front_6th_chapter1-2/issues/7
코드 품질
- 만족스러운 구현:
- (yuhyeon99/front_6th_chapter1-2/issues/4 를 이용한 이벤트 위임:
- 모든 이벤트 리스너를 실제 DOM 노드에 개별적으로 등록하지 않고, 컨테이너 최상단에 한 번만 등록
- 이로 인해 VNode가 업데이트될 때마다 리스너를 해제하고 다시 등록하는 비용이 줄어듬
- yuhyeon99/front_6th_chapter1-2/issues/5 의 재귀적 렌더링 구조:
- VNode 객체를 실제 DOM 요소로 변환하는
renderElement함수를 재귀적으로 구현하여, 복잡한 중첩 구조의 컴포넌트도 간결한 코드로 렌더링할 수 있었습니다.
- VNode 객체를 실제 DOM 요소로 변환하는
- 리팩토링이 필요한 부분:
- yuhyeon99/front_6th_chapter1-2/issues/6 함수의 과도한 책임
- 현재
updateElement함수는 노드 타입 비교, 속성(props) 비교, 자식 노드 비교 및 교체 등 너무 많은 책임을 한 번에 처리하고 있습니다. - 이로 인해 함수가 비대해지고 조건 분기가 복잡해져 가독성과 유지보수성이 떨어집니다.
- 개선 방향:
updatePorps,updateChildren등 각 기능을 명확히 분리된 작은 함수로 리팩토링해야 합니다.- 이로 인해 각 부분의 가독성과 유지보수를 용이하게 만들 수 있습니다.
- 현재
- 코드 설계 관련 고민과 결정:
- diff 알고리즘의 구현 과정
- 처음부터 완벽한 diff 알고리즘을 구현하기보다, 먼저 테스트를 만족하는데 집중하도록 구현하여 전체 렌더링 사이클을 완성하는데 집중했습니다.
- "동작하는 가장 간단한 버전"을 먼저 만들고 점진적으로 개선하는 전략이었습니다.
- 현재 방식은 리스트의 맨 앞에 아이템이 추가되거나 순서가 변경될 때, 모든 자식 노드를 새로 렌더링하는 비효율을 유발합니다.
- 예시 코드:
-
// 6. 자식 노드 diff const newChildren = newNode.children || []; const oldChildren = oldNode.children || []; const newLength = newChildren.length; const oldLength = oldChildren.length; // 1. 자식 노드 업데이트 (공통 길이만큼) for (let i = 0; i < newLength; i++) { updateElement(existingDom, newChildren[i], oldChildren[i], i); } // 2. 남는 old 자식 노드들 제거 (뒤에서부터) if (oldLength > newLength) { for (let i = oldLength - 1; i >= newLength; i--) { const childToRemove = existingDom.childNodes[i]; if (childToRemove) { existingDom.removeChild(childToRemove); } } }- 이 코드는
newChildren과oldChildren배열을 인덱스(index)를 기준으로 순차적으로 비교합니다.(e.g.newChildren[0],oldChildren[0]비교) - 문제점: 원래 자식 노드 리스트가
[A, B, C], 새로운 자식 노드 리스트가[X, A, B, C]일 경우에- X와 A 비교 업데이트 => A와 B 비교 업데이트 => B와 C 비교 업데이트 => C와 undefined(oldNode에 없음) 순서로 진행됨
- 현재 방식 때문에
updateElement는 모두 "다른 노드"로 인식하고 불필요하게 DOM을 새로 생성하거나 변경합니다. - 이는 DOM 조작 비용에 악영향을 미칩니다.
- 이 코드는
-
- 이러한 문제를 각 리스트 아이템에 고유한
key속성을 부여해서 해결할 수 있는 방법이 있다고 확인했고 향후 수정해볼 생각입니다.
학습 효과 분석
처음 이 과제를 접했을 때는 1주차 과제보다 난이도는 쉽다고 생각했지만, 어떻게 시작해야할지에 대한 고민은 더 막막했어요.
곰곰히 생각해보면 이러한 함수들을 왜 써야하고, 어떻게 적용되는지에 대한 이해도가 낮아서 그런 것 같습니다. 그래서, 이슈를 작성하면서 분할과 정복 방식으로 과제를 해결해 나갔습니다.
그러다 보니 작성한 코드에 대해서 이유를 찾게되고, 더 나은 코드에 대해서도 고민할 수 있는 계기가 되어서 한 주간 많이 배우고 학습했습니다.(이런 방식을 소개해준 지훈님. 감사합니다!)
이러한 학습 방법을 통해 한 주간 개발하며 얻은 지식은
- 가상돔
- 리액트(컴포넌트, 상태) 동작 원리
- diff 알고리즘 같은 저에게 생소한 개념들이었습니다.
물론, 다 이해하진 못했고 아직 부족한 점도 많다고 생각합니다. 그래도 이런 식으로 접하게 되고, 나중에 여러 번 복기한다면 더 명확하고 상세하게 이해하게되어 기본기가 탄탄해질거라 믿습니다.
과제 피드백
현재 과제에서 구현하게되는 함수들이 왜 필요한지에 대한 사전 지식이 없는 사람들을 위해서 JSX 트렌스파일에 관한 내용을 좀 더 다뤄보면 좋을 것 같다고 생각했습니다.
좋았던 점은 주어진 함수 내용을 작성하면 해결되는 과제였기에 학습 목적에서 막히는 점 없이 많이 공부할 수 있어서 좋았습니다!
리뷰 받고 싶은 내용
- 함수 내 조건문이 길어지고 있는데 가독성을 위해 분리하거나
early return방식이 더 나은 구간이 있을까요? 예:updateAttributes/createElement내부 - 현재
updateElement에서 타입이 다른 VNode는 교체하고 텍스트 노드는 바로 비교하는 방식이React의Reconciliation과 유사하다고 생각했는데 이 접근 방식에 논리적 허점이나 성능 문제가 있을까요?
과제 피드백
유현님 고생하셨어요~ 작은 단위로 문제를 분할하고 점점 완성해나가는 접근 멋있는데요! 현업에서도 문제가 너무 복잡하게 느껴진다면 많이 사용하는 방식이니 좋은 기회라고 생각하시고 계속해서 연습해보시면 좋겠어요 ㅎㅎ 과제 내용을 보면 명확하게 필요한 부분에 대해서 잘 구현하신 것 같아요. 각 함수에 대한 주석도 깔끔하게 작성해주셨구요 ㅎㅎ
질문 주셨던 부분 답변을 드려보면요.
함수 내 조건문이 길어지고 있는데 가독성을 위해 분리하거나 early return 방식이 더 나은 구간이 있을까요?
이 부분은 사실 얼리 리턴이 적용되어 코드가 좀 더 깔끔해지기보다는 추상화 수준, 그리고 지금은 절차적으로 되어있는 여러 코드들에 대해서 함수형 코딩으로 해당 동작들에 대해 공통 로직들을 분리하고 가독성을 높이는 방식으로 진행해보면 어떨까 싶긴 했어요! 말씀해주신 함수들이 이에 해당했는데요. 관련해서 함수형 코딩의 핵심 원칙같은것들을 미리 공부해보셔도 좋을 것 같고, 함수를 내 기준에 맞춰 추상화 해보고 AI에게 피드백을 받아가면서 적절한 추상화란 무엇인지 개념을 정립해 봐도 좋을 것 같아요. (이 부분은 추후 발제에서도 다루는 내용이니 기대해보셔도 좋을 것 같네요 ㅎㅎ)
현재 updateElement에서 타입이 다른 VNode는 교체하고 텍스트 노드는 바로 비교하는 방식이 React의 Reconciliation과 유사하다고 생각했는데 이 접근 방식에 논리적 허점이나 성능 문제가 있을까요?
말씀해주신대로 지금의 과제 복잡도에서는 충분히 잘 구현해주셨다고 생각이드는데요. 지금의 구조에서는 트리를 대체하는 방식이 조금 비효율 적일 수 있어서 추후에 더 복잡도를 높인다고 한다면, 실제 리액트처럼 키를 기반으로 비교를 진행하고 트리를 기반으로 순회하면서 업데이트 하는 구조를 만드는게 좋지 않을까 싶긴합니다! 관련해서 이미 구현체들이나 설명이 되어있는 글들이 많으니 꼭 함께 학습해보시면 좋을 것 같아요.
고생 많이 하셨고 다음주도 화이팅입니다!