[1팀 신희원] Chapter 1-2. 프레임워크 없이 SPA 만들기 #37
Conversation
|
고생하셧습니다 희원님 해낼줄 알앗다고요~ |
| export function addEvent(element, eventType, handler) { | ||
| if (!eventMap.has(element)) { | ||
| eventMap.set(element, new Map()); | ||
| } | ||
|
|
||
| const elementEvents = eventMap.get(element); | ||
| if (!elementEvents.has(eventType)) { | ||
| elementEvents.set(eventType, new Set()); | ||
| } | ||
|
|
||
| elementEvents.get(eventType).add(handler); | ||
| /** | ||
| * elementEvents 형태 | ||
| * Map(1) { | ||
| * 'click' => Set(1) { | ||
| * [Function: spy] { | ||
| * getMockName: [Function (anonymous)], | ||
| * mockName: [Function (anonymous)], | ||
| * ... | ||
| */ | ||
|
|
||
| if (!delegatedEvents.has(eventType)) { | ||
| delegatedEvents.add(eventType); | ||
|
|
||
| if (rootElement) { | ||
| rootElement.removeEventListener(eventType, handleEvent); | ||
| rootElement.addEventListener(eventType, handleEvent); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
WeakMap, Map , Set을 사용하였는데 적절히 잘 사용한 것인지
| function updateAttributes($el, props) { | ||
| Object.entries(props).forEach(([attr, value]) => { | ||
| if (attr.startsWith("on") && typeof value === "function") { | ||
| const eventType = attr.toLowerCase().slice(2); | ||
| addEvent($el, eventType, value); | ||
| } else if (["checked", "disabled", "selected", "readOnly"].includes(attr)) { | ||
| $el[attr] = Boolean(value); | ||
| } else if (attr === "className") { | ||
| $el.setAttribute("class", value); | ||
| } else if (attr === "style" && typeof value === "object") { | ||
| Object.assign($el.style, value); | ||
| } else { | ||
| $el.setAttribute(attr, value); | ||
| } | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
각 조건문에 early return을 해주면 조건문을 깔끔하게 쓸 수 있습니다!
AS-IS
if (attr.startsWith("on") && typeof value === "function") {
const eventType = attr.toLowerCase().slice(2);
addEvent($el, eventType, value);
} else if (["checked", "disabled", "selected", "readOnly"].includes(attr)) {
$el[attr] = Boolean(value);
}TO-BE
if (attr.startsWith("on") && typeof value === "function") {
const eventType = attr.toLowerCase().slice(2);
addEvent($el, eventType, value);
return
}
if (["checked", "disabled", "selected", "readOnly"].includes(attr)) {
$el[attr] = Boolean(value);
return
}| // Remove null, undefined, boolean (except 0/number) | ||
| function filterValid(child) { | ||
| return !(child === null || child === undefined || typeof child === "boolean"); | ||
| } |
There was a problem hiding this comment.
요런 헬퍼 함수들은 createVNode 밖에 정의하는 게 좋을 것 같습니다. 함수 지역 단위에서 기억해야하는 게 아니라면! createVNode가 실행될 때마다 내부 함수들이 계속 생성돼요. 파일 안에 정의하면 파일이 실행될 때 한번만 생성됩니다~
|
|
||
| export function createElement(vNode) {} | ||
| export function createElement(vNode) { | ||
| if (vNode == null || vNode == undefined || typeof vNode == "boolean") { |
There was a problem hiding this comment.
희원님! 이건 소소한 팁인데 null과 undefined를 느슨한 비교를 통해 동시에 비교하는 문법은 어떠신지 제안 드리려구 가져왔어요!!
toss/frontend-fundamentals#189
토스에서는 null과 undefined를 같이 걸러야 하는 케이스에서 대부분 느슨한 비교를 사용한다고 해요! 참고하십셔!
저는 코드가 보기에 심플해져서 개인적으로 느슨한 비교가 더 좋은 것 같아요
| container.appendChild(element); | ||
| } else { | ||
| // 이후에는 updateElement로 기존 DOM을 업데이트한다. | ||
| updateElement(container, normalizedVNode, oldNode, 0); |
There was a problem hiding this comment.
updateElement 함수 index의 기본값이 이미 0으로 들어가 있어서 여기서는 0을 빼도 같은 동작을 할 것 같아요!
| export function createVNode(type, props, ...children) { | ||
| return {}; | ||
| return { | ||
| type, | ||
| props, | ||
| children: children.flat(Infinity).filter((child) => child != null && child !== false), | ||
| }; | ||
| } |
There was a problem hiding this comment.
flat 매서드를 쓰기전에 reduce로 구현하신것도 참신했어요! 결국 flat 매서드를 알아내셔서 이렇게 깔끔한 코드로 바뀐것을 보니 희원님은 성장의 폭이 가파른 개발자군용! :) !!
과제 체크포인트
배포 링크
기본과제
가상돔을 기반으로 렌더링하기
이벤트 위임
심화 과제
Diff 알고리즘 구현
과제 셀프회고
VirtualDOM 에 대해 특징과 개념을 알게 되었다.
이벤트 위임하는 부분이 진짜 어려웠는데... 어찌저찌 했다... 다시 공부가 필요할 것 같다.
이번에도 많은 개념들을 배웠다. (weakmap , set , 평탄화/정규화)
기술적 성장
createVNode
평탄화를 왜 해주는가?
: 중첩된 배열을 단일 배열로 펼쳐서 다루기 쉽게 만들기 위해서
아래 코드를 보면
map은 배열을 반환하고
nomalizeVNode
정규화를 해주는 이유 : 객체/배열 구조 데이터를 평탄하고 효율적으로 구성하는 것
createElement
entries 사용하여 attr , value 로 배열화하여 element 요소 넣어주기.
eventManager
Map, eventMap
JavaScript에서 키-값 쌍을 저장하는 자료구조지만, 사용 목적과 내부 동작 방식에 큰 차이
📊 Map vs WeakMap 차이 요약
MapWeakMap.size가능메모리 누수 방지 (가비지 컬렉션)
DOM 요소가 삭제되면 WeakMap의 키(= 요소)에 대한 참조도 사라짐
그러면 JavaScript 엔진이 해당 entry를 자동으로 메모리에서 제거함
만약 Map을 사용했다면 eventMap이 el을 계속 참조 → 메모리 누수 발생
하지만 WeakMap은 el이 참조되지 않으면 자동으로 해당 데이터 제거됨
updateElement
두 데이터 구조 간의 차이점(Difference)을 찾아내는 알고리즘
renderElement
코드 품질
코드 리팩토링 필요한 부분
학습 효과 분석
addEvent (이벤트 위임) 과 addEventListener (직접 바인딩) 의 차이 요약표
addEventaddEventListenerevent.target기반, 부모 탐색 필요과제 피드백
createElement.js 파일에서 createDocumentFragment 사용 이유 알아보기..!
리뷰 받고 싶은 내용
[Function: spy] {
안에 있는 내용은 이벤트 핸들러들인가요 ? 무엇인지 궁금합니다.}