이선민 - 자판기 온보딩 프로젝트#19
Conversation
- handleProductPurchase 함수 추가: 상품 구매 처리 로직 구현 - 잔액 부족 시 메시지 표시 기능 추가 - 구매 성공 시 잔액 업데이트 및 로그 추가
- HTML 구조 개선: 정적 마크업으로 UI 구조 변경 - CSS 스타일 수정: 레이아웃 및 디스플레이 속성 조정 - JavaScript 코드 리팩토링: - DOM 요소 참조 방식 개선
- 금액 입력 컨테이너를 form 요소로 변경하여 제출 시 기본 동작 방지 - '투입' 버튼을 form의 submit 타입으로 변경
- HTML에 상품 버튼 템플릿 추가 - JavaScript에서 템플릿을 활용한 상품 그리드 초기화 로직 개선 - DOM 조작 최소화로 성능 향상
- 입력값 파싱 방식 변경: Number.parseInt() 대신 valueAsNumber 속성 사용 - 유효성 검사 로직 개선: NaN 체크 추가
- 숫자가 아닌 문자 제거 정규식을 /[^0-9]/g에서 /\D/g로 변경
- initializeEventListeners 함수에 객체 구조 분해 할당 적용 - initialize 함수에서 필요한 요소만 전달하도록 수정
- 현재 잔액과 투입 금액의 합이 1억원을 초과하는 경우 처리
- JSDoc 스타일의 주석 추가로 함수 설명 및 매개변수 타입 명시 - 주요 객체와 함수에 대한 설명 주석 추가
| updateDisplayAmount(state.currentMoney); | ||
| break; | ||
| } | ||
| }; |
There was a problem hiding this comment.
@taggon
이벤트 처리하는 부분을 한 곳에서 처리해서 경우를 나눈 이유가 아래 코드가 중복되서 하나의 함수로 묶어서 진행했는데요, 이정도 규모이면 따로 풀어서 진행하는게 효율적인 방법인걸까요?
const target = e.target.closest('.product-button'); if (!target) return; const productId = Number.parseInt(target.dataset.productId); const product = PRODUCTS.find((product) => product.id === productId);
const handleClick = (e) => {
const target = e.target.closest('.product-button');
if (!target) return;
const productId = Number.parseInt(target.dataset.productId);
const product = PRODUCTS.find((product) => product.id === productId);
updateDisplayAmount(product.price);
handleProductPurchase(product);
};
const handleMouseOut = (e) => {
const target = e.target.closest('.product-button');
if (!target) return;
const productId = Number.parseInt(target.dataset.productId);
const product = PRODUCTS.find((product) => product.id === productId);
updateDisplayAmount(state.currentMoney);
};
There was a problem hiding this comment.
이런 경우에도 저는 나누는 게 나중에 추적하기가 쉽다고 봐요. 다만 공통된 부분은 다시 별도의 함수로 만들 수 있습니다. 타입도 보고 이해하시라고 편의상 TS로 작성해볼게요.
function getProductFrom(element: HTMLElement) {
const button = element?.closest('.product-button');
const productId = Number.parseInt(button?.dataset.productId);
const product = productId ? PRODUCTS.find(item => item.id === productId) : null;
return product;
}
function handleClick(e: MouseEvent) {
const product = getProductFrom(e.target);
if (product) {
updateDisplayAmount(product.price);
handleProductPurchase(product);
}
}
function handleMouseOut(e: MouseEvent) {
const product = getProductFrom(e.target);
if (product) {
updateDisplayAmount(state.currentMoney);
}
}일단 이렇게 수정할 수 있을 것 같기는 한데 이 코드만 보면 handleMouseOut에서 product를 가져와야 할 이유가 안 보이네요.
| updateDisplayAmount(product.price); | ||
| handleProductPurchase(product); | ||
| break; | ||
| case 'mouseout': |
There was a problem hiding this comment.
이 경우에는 mouseout 보다는 mouseleave가 더 맞을 겁니다. 그리고 addEventListener의 세 번째 인수를 true로 설정하셔야 할테고요. 차이가 뭘까요?
There was a problem hiding this comment.
오! 궁금해서 찾아봤더니 차이가 명확하게 있네요!
mouseleave 이벤트는 요소의 경계를 완전히 벗어날 때만 발생해서 현재 제 코드는 부모 요소인 productGrid에 이벤트를 걸고 있는데, addEventListener의 세 번째 인수인 useCapture 옵션을 이벤트 캡처링 단계에서 이벤트를 처리한다고 true로 설정해서 하는 방법을 말씀주신 거네요. 이 방법대로 수정해보겠습니다 😃
- handleGridEvent 함수를 handleClick과 handleMouseLeave로 분리 - 제품 조회 로직을 getProductById 유틸 함수로 추출 - mouseout 이벤트를 mouseleave로 변경하여 이벤트 버블링 개선
- .gitignore 파일을 현대적인 구조로 정리
- pre-commit 훅을 lint-staged를 사용하도록 변경 - lint-staged 설정에 포맷팅, 린팅, 자동 스테이징 추가
- Proxy 객체를 사용하여 상태 변경 감지 로직 구현 - 금액 표시 업데이트 로직 중복 제거 - forEach를 for...of로 변경하여 반복문 가독성 개선
- product-template을 product-grid 내부로 이동하여 HTML 구조 개선 - 관련 컴포넌트들의 계층 구조를 더 명확하게 정리
- 기능별로 디렉토리 구조 재구성 - dom: DOM 요소 조작 및 참조 관련 코드 - features: 상품과 금액 관련 핵심 기능 - store: 전역 상태 관리 - utils: 포맷팅, 로깅 등 유틸리티 함수 - 관련 기능들을 단일 책임 원칙에 따라 모듈화 - 파일 경로 및 import 구문 정리
- moneyService: deductMoney 함수에서 불필요한 금액 검증 제거 - productService: validatePurchase 함수에서 중복 검증 제거 - Product.js → product.js로 파일명 컨벤션 통일
|
@taggon 기존 작업에서는 테스트를 DOM 요소를 모킹하는 식으로 작성해야해서 아래와 같이 main.js 내 함수를 아래를 기준으로 나누어서 비즈니스 로직 부분만 테스트를 작성해보았습니다. 미진한 부분이 있다면 피드백 부탁드립니다. 🙇
|
1차 리뷰 후 todo 리스트
추가 todo 리스트