과제 체크포인트
배포 링크
https://tooth-is-silver.github.io/front_6th_chapter1-2/
기본과제
가상돔을 기반으로 렌더링하기
- createVNode 함수를 이용하여 vNode를 만든다.
- normalizeVNode 함수를 이용하여 vNode를 정규화한다.
- createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
- 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.
이벤트 위임
- 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
- 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
- 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다
심화 과제
Diff 알고리즘 구현
- 초기 렌더링이 올바르게 수행되어야 한다
- diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
- 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
- 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
- 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다
과제 셀프회고
기술적 성장
- 구현을 완료했다
- 1주차때 기능 구현은 물론 PR도 작성 못하고 배포도 못했는데, 이번에는 기능 구현부터 심화, 배포까지 수월하게 진행했습니다. 정말로 성장했다고 느낍니다.
- 깃헙 액션 배포
- yml파일 기준으로 깃헙 페이지 배포하는 방식을 알게되었습니다.
- yml파일이 없어도 main 페이지 기준으로 마이크로 서비스를 배포 가능하다는 것을 알게되었습니다.
- 이벤트 위임
- 이벤트 위임을 잘못 이해하고 있었는데, 황태영 학메랑 열심히 머리 쥐어짜면서 같이 공부해서 개념을 숙지할 수 있게 되었습니다. 태영 학메 감사합니다. 고맙습니다.
- 이벤트 위임은 무조건 root에서 모든 이벤트를 등록하는것이 아닌, 상위 요소에 등록하여 하위 요소로 이벤트를 분배하고 이벤트가 시작한 지점의 요소가 이전에 등록한 이벤트의 요소가 맞는지 확인하여 이벤트를 동작시키는 방식입니다.
- 이벤트 위임시 등록하는 이벤트에 event객체를 전달합니다. 등록된 이벤트는
e.preventDefault(),e.stopPropagation()과 같은 메서드를 사용할 수 있습니다. 자주 사용하는 이벤트 메서드인데 내부 로직을 파악할 수 있었던 계기였습니다. - 다수의 동일한 요소에 동일한 핸들러 등록시 메모리 성능 이슈가 발생할 수 있기때문에, 이벤트 위임은 동일한 요소에 동일한 핸들러를 1번만 등록하여 사용할 수 있도록 도와줍니다.
- oldNode와 newNode
- 리렌더시 그냥 단순하게 element를 갈아끼운다는 개념으로 이해하고 있었는데 이 기회에 정확히 집고 넘어갈 수 있었습니다.
- oldNode는 이전 요소이며 newNode와 비교하여 type이 같은지, 텍스트는 아닌지, oldNode는 없었다가 newNode에 요소가 생겼는지, oldNode에는 있었다가 newNode에는 사라지지 않았는지 모든 조건을 체크해본 후에 재귀방식으로 내부 children부터 하나씩 교체해나갑니다. 단순히 '변경된 부분만 찾아서 교체하는 알고리즘이 있겠지'하고 넘어갔던 부분이었는데 왜 리엑트가 큰 프로젝트일 수록 속도나 성능 이슈가 발생될 수 있는지 알게 된 부분이었습니다.
- 디버깅 방법
- vitest로 Debug test하는 방법을 습득했습니다. 중단점도 추가해서 좀 더 자세히 알아보고 싶은 부분도 체크해 볼 수 있습니다. 콘솔로그만 활용하다가 한 줄 한 줄씩 어떻게 데이터가 들어가고 나가는지 정확히 파악이 가능해지니 디버깅 속도가 증가하는걸 느꼈습니다.
- preact로 동작 방식 확인
- 초기 함수 구현시 preact github 리소스가 참 도움이 많이 됐습니다. 처음 만들어보는 SPA라서 발제 설명을 확인해봐도 뜬 구름 잡는 느낌이었는데, preact 소스를 찬찬히 보면서 내 코드에 어떻게 적용해야할지 감을 잡을 수 있게 된 것 같습니다.
코드 품질
-
특히 만족스로운 구현
-
eventManager.js를 구현 후 리팩토링하면서 초반과 많이 달라지지 않은 코드입니다.
해당 함수를 완벽히 이해하고 나서 팀에서 설명해달라고 요청이 들어왔을때setupEventListeners,addEvent,removeEvent를 왜 사용하는지, 그리고 사용하는 부분에서 어떻게 해당 함수를 요청하는지에 대해서 정확히 그리고 내가 생각하고 있는 지식을 곁들여서 얘기할 수 있었기 때문에 애착이 더 가는 코드입니다.const eventRegistry = new Map(); const eventHandlerMap = {}; export function setupEventListeners(root) { for (const event in eventRegistry) { if (eventRegistry[event]) { root.removeEventListener(event, eventHandlerMap[event]); } const handler = (e) => { eventRegistry[event].forEach(({ element, handler }) => { if (element.contains(e.target)) { handler(e); } }); }; eventHandlerMap[event] = handler; root.addEventListener(event, handler); } } export function addEvent(element, eventType, handler) { if (!eventRegistry[eventType]) { eventRegistry[eventType] = new Set(); } eventRegistry[eventType].add({ element, handler }); } export function removeEvent(element, eventType, handler) { if (eventRegistry[eventType]) { eventRegistry[eventType].forEach((item) => { if (item.element === element && item.handler === handler) { eventRegistry[eventType].delete(item); } }); } }
-
-
코드 설계 관련 고민과 결정
- 1주차와 2주차 과제 진행시 수정이 필요한 부분은 적극적으로 수정하고 왜 수정했는지 이유를 명확히 기재하면 괜찮다는 코치님의 조언이 있습니다. 그래서 다른 분들의 코드를 보며 async로 바꿔볼까, 함수에 전달하는 인자들을 바꾸어볼까, 리팩토링을 해볼까 했지만 지금은 기능 구현에 중점을 두고 진행하는 것으로도 충분하다고 생각하여 욕심을 조금 내려놓기로 하였습니다.
- github page 배포시
ci.yml파일을 활용하여main브랜치 푸시로 진행할지deploy.yml파일을 추가하여 github action을 추가로 돌릴지 고민이었습니다. 이번에는deploy.yml파일을 추가하여 yml파일의 배포 플로우를 모두 제 것으로 습득 한 후에 다음 배포때는ci.yml파일만으로 진행해보려고합니다.ci.yml파일 내부에서deploy.yml동작 방식까지 한 꺼번에 진행하는 경우가 있을까 찾아보았으나, 역할 분리로 인해 보통은 ci/cd를 따로 관리하는 것을 추천한다고 합니다.
학습 효과 분석
과제 피드백
-
과제에서 모호하거나 애매했던 부분
updateElement.js의 child를 확인하여 하나씩 업데이트 하는 부분인데, 초기에 for문으로 구현했다가 동작안하는 부분이 있어 while문으로 다시 구현 후, 리팩토링하여 for문으로 교체했습니다. 이 때, while문에서 for문으로 변경한 이유는 취향적인 부분도 있으나 while문으로 동작시 동적으로 변경되는 값에 참조 문제가 있을 수 있다는 얘기를 들었기 때문입니다. 진짜로 참조 문제가 발생할 가능성이 있는지, 그래서 for문이 더 나은 방식인지 궁금합니다.// 이전 while문 while (i < maxChildrenLength) { const newChild = newChildren[i]; const oldChild = oldChildren[i]; if (!newChild && oldChild) { updateElement(target, null, oldChild, i); maxChildrenLength -= 1; } else { updateElement(target, newChild, oldChild, i); i += 1; } } // 변경한 for문 for (let i = 0; i < maxChildrenLength; ) { const newChild = newChildren[i]; const oldChild = oldChildren[i]; if (!newChild && oldChild) { updateElement(target, null, oldChild, i); maxChildrenLength -= 1; } else { updateElement(target, newChild, oldChild, i); i += 1; } }
-
과제에서 좋았던 부분
- 앞서 얘기했던 기술적 성장을 경험한 것 😎
리뷰 받고 싶은 내용
-
eventManager.js파일 내부에 eventRegistry를 new Map()을 사용하여 구성했습니다. 이벤트 자체를 키로 사용하기 위해서 이와 같은 방법을 사용했는데 다른 분들의 코드를 보니 일반 객체나 new WeekMap()으로도 사용하는 경우도 확인했습니다. 동작상으로는 크게 문제가 없을 것 같긴한데 차이점이 있다면 어떤 차이점이 있는지 궁금합니다.
5팀 이지훈님 PR - 객체 2팀 유윤우님 PR - new WeakMap -
createElement.js코드에서 예외처리가 모두 안 된 것 같은 찝찝함이 있습니다. 테스트 코드와 현재 구현된 UI 기능 동작으로는 어떤 예외처리가 더 필요한지 판단하기 어려워 누락된 예외처리가 있는지 궁금합니다. 꼭createElement.js가 아니더라도 다른 부분의 예외처리가 누락된 부분이 있다면 알려주시면 정말 감사하겠습니다.
과제 피드백
가은님 고생하셨어요! 회고 남겨주신거 읽어보니, 새로운 개념 뿐만 아니라 잘못 알고 계셨던 개념까지 꽤나 명확하게 될 수 있는 좋은 시간이였네요. Preact 구현을 참고하는 것도 매우 좋은 접근이였네요 :+1 :+1
질문 주셨던 부분으로 넘어가 보면요! 결국 첫번째 질문은 말 그대로 각 객체, Map, WeakMap의 자료 구조의 차이와 이어질 것 같은데요. 각 자료구조는 프로토타입을 다루는 거나 순회하는 부분, 특히 GC부분에서 동작이 차이가 많이 날 것 같아요. (키에 들어갈 수 있는 부분도요!) 과제에 있어서 결국 구현을 어떻게 하냐에 따라서 '동작상'으로는 큰 차이는 없을 수 있는데요. 위 차이에 있어서 내가 관리하기 쉬운 방식은 무엇인지 명확하게 비교하고 선택할 수 있다면 큰 차이는 없을 것 같아요~
두번째 질문은 예외처리 관련한 질문이였는데요. 지금 제가 살펴봤을때는 더~챙겨야 되는 예외처리는 크게 없어보여요! 충분히 잘 구현해주셨던것 같아요 ㅎㅎ 걱정이 되신다면 각 작업들이 실패하는 케이스에 있어서 폴백동작들만 잘 정의해주심되지 않을까 싶긴한데 굳이 싶긴하네요!
아무튼 잘 구현해주셨고 다음주 과제도 화이팅 하시길 바랍니다!