👩‍💻
Megaptera Frontend
  • 주차별 학습
    • megaptera-front
    • 1. 개발 환경
      • 1. 개발 환경
      • 2. TypeScript
      • 3. React
      • 4. Testing Library
      • 5. Parcel & ESLint
      • 한 주를 마치며
    • 2. JSX
      • 1. JSX
      • 한 주를 마치며
    • 3. React로 사고하기
      • 1. React Component
      • 2. React State
      • 한 주를 마치며
    • 4. React Hooks
      • 1. Express
      • 2. Fetch API & CORS
      • 3. React의 Hook
      • 4. useRef & Custom Hook
      • 5. usehooks-ts
      • 한 주를 마치며
    • 5. 테스트
      • 1. TDD
      • 2. React Testing Library
      • 3. MSW
      • 4. Playwright
      • 한 주를 마치며
    • 6. External Store
      • 1. External Store
      • 2. TSyringe
      • 3. Redux 따라하기
      • 4. usestore-ts
      • 한 주를 마치며
    • 7. React Router
      • 1. Routing
      • 2. Routes
      • 3. Router
      • 4. Navigation
      • 한 주를 마치며
    • 8. CSS in JS
      • 1. Design System
      • 2. Style Basics
      • 3. CSS in JS
      • 4. styled-components
      • 5. props와 attrs
      • 6. Global Style & Theme
      • 한 주를 마치며
    • 9. 쇼핑몰 목록, 상품 페이지
      • 1. 개발하기 전 준비
      • 2. 목록 보기
      • 3. 상품 상세 보기
      • 4. 장바구니 보기
      • 5. 장바구니에 상품 담기
      • 한 주를 마치며
    • 10. 사용자 인증, 인가
      • 1. 로그인
      • 2. 로그아웃
      • 3. 회원가입
      • 4. 주문 목록 & 주문 상세
      • 한 주를 마치며
    • 11. 주문, 결제
      • 1. 배송 정보 입력
      • 2. 포트원 결제 요청
      • 3. 배송 및 결제 정보 전달
      • 한 주를 마치며
    • 12. 어드민
      • 1. 관리자 웹 사이트 개발 시작
      • 2. 로그인, 사용자 목록
      • 3. 카테고리 관리
      • 4. 주문 관리
      • 5. 상품 관리
      • 한 주를 마치며
Powered by GitBook
On this page
  • 1. 상품 상세 데이터 받아오기
  • ProductDetailPage.tsx 작성
  • useProductDetailStore hook 생성
  • ProductDetailStore.ts 생성
  • Null Object 생성
  • useFetchProduct hook 생성
  • 2. 상품 상세 페이지 UI 구현하기
  • ProductDetail.tsx 컴포넌트 구현
  • Images 컴포넌트 생성
  • Description 컴포넌트 생성
  1. 주차별 학습
  2. 9. 쇼핑몰 목록, 상품 페이지

3. 상품 상세 보기

Previous2. 목록 보기Next4. 장바구니 보기

Last updated 2 years ago

1. 상품 상세 데이터 받아오기

🎯 상품 상세 정보를 얻어서 보여주는 페이지 구현하기

  • 상품을 찾을 수 없는 경우를 따로 구분해서 표현하면 좋음

  • 지금은 구분하지 않고 그냥 일반 에러로 표시할 것

ProductDetailPage.tsx 작성

useFetchProduct에서 loading과 error를 받아 화면에 표현하기

  • Loading

  • Error

  • ProductDetail

useProductDetailStore hook 생성

  • src/hooks

  • tsyringe의 container를 이용해 ProductDetailStore를 활용

export default function useProductDetailStore() {
  const store = container.resolve(ProductDetailStore);
  return useStore(store);
}

ProductDetailStore.ts 생성

  • src/stores

구현 내용

  1. 로딩 시작

    • loading = true

    • error = false

  2. apiService.fetchProduct({productId})

    • ApiService에서 fetchProduct 구현

    • data를 그대로 product로 내보내기

  3. 로딩 끝

    • loading = false, error는 상태 봐서 처리

    • product

타입 지정

// 초기값을 null로 잡을 수 있음 
product: ProductDetail | null = null;

// null을 계속 체크해야하는 상황이나 에러를 피하기 위해 사용 ✅
product: ProductDetail = nullProductDetail;
  • Null Object를 생성

@Action() 추가

  • startLoading

  • setProduct

  • setError

Null Object 생성

types.ts의 하단에 생성하거나 그 외의 곳도 가능

export const nullProductDetail: ProductDetail = {
  id: '',
  category: { id: '', name: '' },
  images: [],
  name: '',
  price: 0,
  options: [],
  description: '',
};

useFetchProduct hook 생성

  • src/hooks

export default function useFetchProduct({ productId }: {
  productId: string;
}): {
  loading: boolean;
  error: boolean;
} {
  const [{ loading, error }, productDetailStore] = useProductDetailStore();

  useEffect(() => {
    productDetailStore.fetchProduct({ productId });
  }, [productDetailStore, productId]);

  return { loading, error };
}

2. 상품 상세 페이지 UI 구현하기

  • 장바구니 담기 기능 때문에 prop drilling 문제가 발생할 수 있어서, Page에서 product를 내려주지 않게 처리

  • ProductDetail 컴포넌트에서 product만 얻어서 활용할 것

ProductDetail.tsx 컴포넌트 구현

  • src/components/product-detail/ProductDetail.tsx

구현 내용

  • Store 에서 상품 정보 얻기 - fetch는 page에서 처리

  • 보여주기

const Container = styled.div`
  display: flex;
  justify-content: space-between;

  aside {
    width: 38%;
  }

  article {
    width: 60%;
  }
`;

export default function ProductDetailView() {
  const [{ product }] = useProductDetailStore();

  return (
    <Container>
      <aside>
        <Images images={product.images} />
      </aside>
      <article>
        <h2>{product.name}</h2>
        <AddToCartForm />
        <Description value={product.description} />
      </article>
    </Container>
  );
}

Images 컴포넌트 생성

const Thumbnail = styled.img.attrs({
  alt: 'Product Image',
})`
  display: block;
  width: 100%;
  aspect-ratio: 1/1;
`;

type ImagesProps = {
  images: Image[];
}

export default function Images({ images }: ImagesProps) {
  const [image] = images;

  if (!image) {
    return null;
  }

  return (
    <Thumbnail src={image.url} />
  );
}

Description 컴포넌트 생성

  • 임의로 key를 잡아주기 위해 컴포넌트 밖에서 function key(text, index)를 생성

function key(value: string, index: number) {
  return `${index}-${value}`;
}

const Container = styled.div`
  li {
    min-height: 1rem;
    line-height: 1.4;
  }
`;

type DescriptionProps = {
  value: string;
}

export default function Description({ value }: DescriptionProps) {
  if (!value.trim()) {
    return null;
  }

  const lines = value.split('\n');

  return (
    <Container>
      <ul>
        {lines.map((line, index) => (
          <li key={key(line, index)}>
            {line}
          </li>
        ))}
      </ul>
    </Container>
  );
}

Special Case(null object pattern)
🔗 실습 링크 : 상품 상세 보기 페이지 UI 구현
🔗 실습 링크 : 상품 상세 보기 로딩 및 에러 처리 구현