Skip to content
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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

2Estella
Copy link

@2Estella 2Estella commented Jan 14, 2025

과제 체크포인트

기본과제

  • React의 hook 이해하기

  • 함수형 프로그래밍에 대한 이해

  • Component에서 비즈니스 로직을 분리하기

  • 비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기

  • Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?

  • 주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?

  • 계산함수는 순수함수로 작성이 되었나요?

심화과제

  • 뷰데이터와 엔티티데이터의 분리에 대한 이해

  • 엔티티 -> 리파지토리 -> 유즈케이스 -> UI 계층에 대한 이해

  • Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?

  • 주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?

  • 계산함수는 순수함수로 작성이 되었나요?

  • 특정 Entitiy만 다루는 함수는 분리되어 있나요?

  • 특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?

  • 데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?

과제 셀프회고

이번 과제를 통해 리팩토링에 대한 고민을 깊게 하고 여러 시도를 해볼 수 있었습니다. 발제에서 언급된대로 비즈니스 로직에서 데이터 흐름을 파악하고 계산 로직을 분리하려는 노력을 했지만, 그 과정이 쉽지 않았습니다.
함수형 프로그래밍이나 순수함수, 뷰데이터와 엔티티데이터 분리에 대해 좀 더 명확히 이해하고 적용하는 것이 필요하다는 점을 깨달았습니다. 차근차근 연습하며 개념을 확실히 익히고, 더 나은 구조를 만들기 위한 방법을 고민해야 할 것 같습니다.

과제에서 좋았던 부분

  • 기존의 코드를 분석해보고, 분리가 필요한 부분을 커스텀 훅을 작성해볼 수 있어서 좋았습니다.
  • 훅과 유틸함수처럼 특정 로직을 테스트하는 코드를 작성해 볼 수 있어서 좋았습니다.
  • 리팩토링을 하면서 관심사 분리에 대해서 고민해볼 수 있어서 좋았습니다.

과제를 하면서 새롭게 알게된 점

  • 액션과 순수함수 분리를 잘 해야하는 이유를 명확히 알게 된 과제였던 것 같습니다. 코드의 유지보수성을 높이고 예측 가능한 코드가 되어 테스트하기가 수월해진다는 것을 알게 되었습니다.
  • 테스트 코드 작성 경험이 거의 없었는데, vitest의 기본 기능들을 익히고, 테스트 페이지를 렌더링해서 사용하거나 특정 로직에 대한 테스트를 작성하는 방법을 배웠습니다.

과제를 진행하면서 아직 애매하게 잘 모르겠다 하는 점, 혹은 뭔가 잘 안되서 아쉬운 것들

  • 뷰데이터와 엔티티데이터의 분리가 아직은 어렵게 느껴지는 것 같습니다. 이 부분은 추가적으로 공부도 하고, 많이 연습도 해봐야할 것 같습니다.
  • 리액트가 익숙하지 않아 컴포넌트 분리하는 부분이 아쉬웠습니다. 조금 더 세분화 하거나 정리할 수 있을 것 같은데.. 시간 될 때 추가적으로 정리 해보며 익혀봐야겠습니다.

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문

  • 커스텀 훅이나 유틸 함수를 만드려고 할 때, 가장 많이 드는 고민이 '과연 이걸 다른 컴포넌트에서 사용할 일이 있을 것인가' 였습니다. 과제뿐만이 아니라 실무에서도 많이 드는 생각인데요! 코치님께서는 어떤 기준을 가지고 만드시는지 궁금합니다.

- 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` 함수 사용
- 상태 업데이트 시 로컬스토리지 자동 저장
@2Estella 2Estella changed the title [WIP] [10팀 이송이] [Chapter 2-2] 디자인 패턴과 함수형 프로그래밍 [10팀 이송이] [Chapter 2-2] 디자인 패턴과 함수형 프로그래밍 Jan 17, 2025
Copy link

@ywkim95 ywkim95 left a 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('유틸리티 함수 테스트', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

계산함수와 커스텀 훅에 대한 테스트까지 작성하셨군요!
굉장히 긴걸보니 작성하시는데 고생 꽤나 하셨을 것 같아요!
대단하십니다!

Comment on lines +57 to +60
<Navigation
buttonText={isAdmin ? '장바구니 페이지로' : '관리자 페이지로'}
onClick={() => setIsAdmin((prev) => !prev)}
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네비게이션 컴포넌트로 분리 하셨네요! 저는 이 부분 되게 좋은듯해요!

Comment on lines +25 to +36
productList={productList}
couponList={couponList}
cart={cart}
addToCart={addToCart}
removeFromCart={removeFromCart}
updateQuantity={updateQuantity}
applyCoupon={applyCoupon}
selectedCoupon={selectedCoupon}
calculateTotal={calculateTotal}
getMaxDiscount={getMaxDiscount}
getRemainingStock={getRemainingStock}
getAppliedDiscount={getAppliedDiscount}
Copy link

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}
    />
  );

Copy link

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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우와... 작성하시는데 고생 많으셨습니다 ㅠㅠ

Comment on lines +47 to +56
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;
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 forEach가 아닌 map으로 변경해보기 도전! 어떠세요..?
고민하실수록 좋은 결과가 나올 것 같아요..!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useLocalStorage를 만드셨군요...!
저는 테스트에서 활용하기 바빠서 테스트 코드 내에서 처리하도록 했는데 굉장히 깔끔하게 만드신 것 같아요...!
잘 배워갑니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

같은 폴더 내에 type과 계산 함수와 훅이 같이 있는 이유가 있나요?!
해당 파일에서만 사용하시기 때문에 그런걸까요??

Comment on lines +84 to +93
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[]);
Copy link

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 = (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용하지 않으시는 코드 같아요...!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants