-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[10팀 이송이] [Chapter 2-2] 디자인 패턴과 함수형 프로그래밍 #17
base: main
Are you sure you want to change the base?
Conversation
- Prettier, ESLint 설치 및 설정 - husky 설정하여 커밋 전 포맷팅 되도록 설정 - tsconfig.app.json 팀 컨벤션으로 수정 - package.json 스크립트 추가
- ESLint와 Prettier에 맞춰 코드 포맷팅 - 코드 스타일 일관성 유지
- 코드 스타일 일관성 유지 - 컨벤션 네이밍 규칙에 맞게 변수명 변경(products > productList) - 불필요한 확장자 제거
- 제품 목록 상태 관리 훅 생성 - 제품 추가 및 업데이트 로직 구현 - 순수 함수(manageProducts)를 활용하여 상태 관리 분리 - 중복 로직 제거 및 가독성 향상
- `calculateItemTotal`: 상품 가격과 할인율을 반영하여 총액을 계산하는 로직 추가 - `calculateCartTotal`: 장바구니 총액 계산 시 아이템별 총액 및 쿠폰 할인 적용 로직 개선 - `updateCartItemQuantity`: 장바구니 아이템 수량을 업데이트하고, 수량이 0일 경우 항목을 제거하는 로직 수정 - `getMaxApplicableDiscount`: 아이템에 대해 적용 가능한 최대 할인율을 계산하는 로직 추가 - 할인 계산 로직을 `calculateTotalWithDiscount` 함수로 분리하여 가독성 및 유지보수성 향상
- 장바구니 상태 관리 훅 생성 - 장바구니 상품 추가 및 업데이트 로직 구현 - 상품 제거 로직 구현 - 장바구니 총액 계산 로직 구현
- 쿠폰 목록 상태 관리 훅 생성 - 쿠폰 추가 및 적용 로직 구현
- 컴포넌트를 별도로 분리하여 네비게이션 기능 구현
- types 폴더 생성 및 타입 이동 - 인터페이스명 변경 및 컨벤션 적용 - 관련 코드에서 변수명 일괄 수정 - 스타일 수정(불필요한 padding 값 제거)
- ProductForm, InputField 컴포넌트를 생성하여 재사용성과 코드 가독성 향상 - 해당 컴포넌트 적용
- props 추가(placeholder, className) 및 구조 개선 - 어드민페이지에 해당 컴포넌트 반영
- UI를 분리하여 컴포넌트를 생성하여 재사용성과 코드 가독성 향상 - 해당 컴포넌트 적용
- components > components/admin으로 이동 및 경로 수정 - DiscountForm 컴포넌트 생성 - InputField 구조 및 라벨 스타일 수정
- AdminPageUI 컴포넌트 생성하여 UI와 비즈니스 로직 분리 - 관련 컴포넌트(DiscountForm, ProductForm) 업데이트
- CartPageUI 컴포넌트 생성하여 UI와 비즈니스 로직 분리 - UI 컴포넌트(CartItem, CouponSelector, OrderSummary, ProductItem) 생성
- 할인 계산을 위한 훅 생성 - 최대 할인율, 남은 재고, 적용된 할인율 계산 로직 구현 - 할인율 변경 시 상태 업데이트 최적화
- useDiscountCalculator 훅 적용 - props 정리 및 불필요한 로직 제거
- 제품 정보 업데이트 및 할인 관리 함수 추가 - 해당 유틸 함수 적용
- updateProductField 함수 테스트: - 제품 필드를 정상적으로 업데이트하는 경우 확인 - 제품이 없을 경우 아무 작업도 하지 않는 경우 확인 - handleAddOrRemoveDiscount 함수 테스트: - 할인을 정상적으로 추가하는 경우 확인 - 할인을 정상적으로 제거하는 경우 확인 - 제품이 없을 경우 아무 작업도 하지 않는 경우 확인
- useCart 훅에서 장바구니에 상품 추가 후 상태 및 최종 금액 계산 확인 - 장바구니에서 상품 삭제 후 상태 및 최종 금액 계산 확인 - 상품 추가 및 삭제에 따른 장바구니 상태 변화 및 할인 금액 계산 테스트 추가
- useCoupons 훅: - 초기 쿠폰 목록과 선택된 쿠폰을 초기화하는 테스트 추가 - 새로운 쿠폰을 추가하는 테스트 추가 - 쿠폰을 적용하는 테스트 추가 - 선택된 쿠폰을 정상적으로 적용하는 테스트 추가 - useDiscountCalculator 훅: - 할인 계산 기능에 대한 테스트 추가 - 정상적인 할인 금액 계산 테스트 추가 - useProducts 훅: - 상품 목록 가져오기 테스트 추가 - 상품 필터링 및 정렬 기능 테스트 추가
- 로컬스토리지에 데이터를 저장하고 불러오는 커스텀 훅 `useLocalStorage` 추가 - 데이터 유효성 검사를 위한 `validateFn` 함수 사용 - 상태 업데이트 시 로컬스토리지 자동 저장
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번 주차 고생 많으셨습니다..!
꼼꼼하게 주석을 달아 주셔서 어떤 로직인지 빠르게 파악이 되었어요...!
또 useLocalStorage를 구현하신걸 보니 저도 더 열심히 해야겠다는 생각이 드네요!
test('새로운 유틸 함수를 만든 후에 테스트 코드를 작성해서 실행해보세요', () => { | ||
expect(true).toBe(false); | ||
}) | ||
describe('유틸리티 함수 테스트', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
계산함수와 커스텀 훅에 대한 테스트까지 작성하셨군요!
굉장히 긴걸보니 작성하시는데 고생 꽤나 하셨을 것 같아요!
대단하십니다!
<Navigation | ||
buttonText={isAdmin ? '장바구니 페이지로' : '관리자 페이지로'} | ||
onClick={() => setIsAdmin((prev) => !prev)} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네비게이션 컴포넌트로 분리 하셨네요! 저는 이 부분 되게 좋은듯해요!
productList={productList} | ||
couponList={couponList} | ||
cart={cart} | ||
addToCart={addToCart} | ||
removeFromCart={removeFromCart} | ||
updateQuantity={updateQuantity} | ||
applyCoupon={applyCoupon} | ||
selectedCoupon={selectedCoupon} | ||
calculateTotal={calculateTotal} | ||
getMaxDiscount={getMaxDiscount} | ||
getRemainingStock={getRemainingStock} | ||
getAppliedDiscount={getAppliedDiscount} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
약간의 꿀팁 아닌 꿀팁을 드리자면... 이름이 동일한 내역에 대해서는 ...props
를 넣어도 문제가 없답니다..!
const cartProps = useCart();
const discountProps = useDiscountCalculator(cart);
return (
<CartPageUI
{...cartProps}
{...discountProps}
/>
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확장성을 고려하시면서 고민해보시면 더 좋은 코드가 나올 것이라고 생각이 듭니다...!
예를 들어 InputFieldProps
는 extends를 사용해서 interface InputFieldProps extends InputHTMLAttributes<HTMLInputElement> {}
라는 방법으로도 접근할 수 있는 것도 있어요!
import { ProductForm } from './ProductForm'; | ||
import { DiscountForm } from './DiscountForm'; | ||
|
||
interface Props { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
우와... 작성하시는데 고생 많으셨습니다 ㅠㅠ
cart.forEach((item) => { | ||
const discount = getAppliedDiscount(item); | ||
const currentDiscount = appliedDiscounts.get(item.product.id); | ||
|
||
// 할인율이 변경된 경우에만 상태 업데이트 | ||
if (discount !== currentDiscount) { | ||
newAppliedDiscounts.set(item.product.id, discount); | ||
discountsChanged = true; | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 forEach가 아닌 map으로 변경해보기 도전! 어떠세요..?
고민하실수록 좋은 결과가 나올 것 같아요..!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useLocalStorage를 만드셨군요...!
저는 테스트에서 활용하기 바빠서 테스트 코드 내에서 처리하도록 했는데 굉장히 깔끔하게 만드신 것 같아요...!
잘 배워갑니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
같은 폴더 내에 type과 계산 함수와 훅이 같이 있는 이유가 있나요?!
해당 파일에서만 사용하시기 때문에 그런걸까요??
if (item.product.id === productId) { | ||
const updatedQuantity = Math.max(0, Math.min(newQuantity, item.product.stock)); | ||
if (updatedQuantity > 0) { | ||
updatedCart.push({ ...item, quantity: updatedQuantity }); | ||
} | ||
} else { | ||
updatedCart.push(item); | ||
} | ||
return updatedCart; | ||
}, [] as CartItemType[]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이중 조건문을 한 번 나눠보시는걸 추천드려요!
* @param {(updatedProduct: ProductType) => void} onProductUpdate - 제품 목록을 업데이트하 | ||
* @param {React.Dispatch<React.SetStateAction<ProductType | null>>} setEditingProduct - 편집 중인 제품 상태 업데이트 함수. | ||
*/ | ||
export const handleAddOrRemoveDiscount = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용하지 않으시는 코드 같아요...!
과제 체크포인트
기본과제
React의 hook 이해하기
함수형 프로그래밍에 대한 이해
Component에서 비즈니스 로직을 분리하기
비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
계산함수는 순수함수로 작성이 되었나요?
심화과제
뷰데이터와 엔티티데이터의 분리에 대한 이해
엔티티 -> 리파지토리 -> 유즈케이스 -> UI 계층에 대한 이해
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
계산함수는 순수함수로 작성이 되었나요?
특정 Entitiy만 다루는 함수는 분리되어 있나요?
특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?
데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?
과제 셀프회고
이번 과제를 통해 리팩토링에 대한 고민을 깊게 하고 여러 시도를 해볼 수 있었습니다. 발제에서 언급된대로 비즈니스 로직에서 데이터 흐름을 파악하고 계산 로직을 분리하려는 노력을 했지만, 그 과정이 쉽지 않았습니다.
함수형 프로그래밍이나 순수함수, 뷰데이터와 엔티티데이터 분리에 대해 좀 더 명확히 이해하고 적용하는 것이 필요하다는 점을 깨달았습니다. 차근차근 연습하며 개념을 확실히 익히고, 더 나은 구조를 만들기 위한 방법을 고민해야 할 것 같습니다.
과제에서 좋았던 부분
과제를 하면서 새롭게 알게된 점
과제를 진행하면서 아직 애매하게 잘 모르겠다 하는 점, 혹은 뭔가 잘 안되서 아쉬운 것들
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문