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

[오하영] Sprint 5 #129

Merged

Conversation

fiivxyxxng
Copy link
Collaborator

@fiivxyxxng fiivxyxxng commented Mar 8, 2025

요구사항

기본

공통

  • Github에 스프린트 미션 PR을 만들어 주세요.
  • React를 사용해 진행합니다.

중고마켓 페이지

  • PC, Tablet, Mobile 디자인에 해당하는 중고마켓 페이지를 만들어 주세요.
  • 중고마켓 페이지 url path는 별도로 설정하지 않고, '/'에 보이도록 합니다.
  • 상단 네비게이션 바, 푸터는 랜딩 페이지와 동일한 스타일과 규칙으로 만들어주세요.
  • 상품 데이터는 https://panda-market-api.vercel.app/docs/에 명세된 GET 메소드 "/products" 를 활용해주세요.
    • 상품 목록 페이지네이션 기능을 구현합니다.
    • 드롭 다운으로 "최신 순" 또는 "좋아요 순"을 선택해서 정렬을 구현하세요.
    • 상품 목록 검색 기능을 구현합니다.
  • 베스트 상품 데이터는 https://panda-market-api.vercel.app/docs/에 명세된 GET 메소드 "/products"의 정렬 기준 favorite을 사용해주세요.

심화

공통

  • 커스텀 hook을 만들어 필요한 곳에 활용해 보세요.

중고마켓 페이지

  • 중고 마켓의 카드 컴포넌트 반응형 기준은 다음과 같습니다.
    • 베스트 상품
      • Desktop : 4열
      • Tablet : 2열
      • Mobile : 1열
    • 전체 상품
      • Desktop : 5열
      • Tablet : 3열
      • Mobile : 2열
    • 반응형에 따른 페이지 네이션 기능을 구현합니다.
      • 반응형으로 보여지는 물품들의 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다.

주요 변경사항

스크린샷

image image image image image image

멘토에게

  • 로컬에서는 검색기능이 작동하는데 배포링크에서는 안되는 것 같은데 확인 부탁드립니다..
  • css를 적용하면서 이미지가 없는 상품 데이터들이 포함된 리스트는 자꾸 비율이 이상하게 돼서 max, min 높이와 넓이를 모두 적용시켰는데 저런 상황에서도 일반적으로 width, height를 적용할 수 있는 좋은 방법이 있을까요?
  • 디자인 패턴을 공부해야 할 것 같은데 컴포넌트를 저정도로만 나눠도 되는지 아니면 버튼이나 검색창 등 더 세분화해서 컴포넌트를 분리하는 것이 좋은지 잘 모르겠습니다.
  • 페이지네이션을 더 효율적으로 구현할 수 있는 방법이 있는지 궁금합니다.

Copy link
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

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

[P0]
리액트는 JSX 문법을 사용하기에 모든 리액트 컴포넌트 파일의 확장자를 .jsx로 변경하셔야 합니다.

Copy link
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

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

제 코드리뷰 앞에 붙어있는 태그들의 의미는 아래와 같습니다.

  • P0 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
  • P1 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
  • P2 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

코드가 전반적으로 깔끔한 것 같습니다. 작성하시느라 고생하셨습니다!
처음에 남겨둔 확장자 관련 코멘트는 꼭 고쳐주셔야 합니다!

Comment on lines +27 to +30
const handleLoad = async () => {
const data = await getProducts(1, 250);
setItems(data.list);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

[P0]
async await 를 통해 API를 호출하는 경우 try catch 문을 통해 에러 핸들링을 해주는 것이 좋습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

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

확인해보니 src/api/api.js에서 try catch를 처리하셨네요.
이런 경우에는 API 코드내에 try catch 문이 있는 경우에는 해당 catch 부분에서 console 뿐만이 아닌 에러 토스트등을 통한 사용자에게 API 에러가 났음을 알리고 재시도할 수 있는 환경을 만들어주면 좋습니다.

Comment on lines +17 to +25
const updatePageSize = () => {
if (window.matchMedia("(max-width: 743px)").matches) {
setPageSize(4);
} else if (window.matchMedia("(max-width: 1199px)").matches) {
setPageSize(6);
} else {
setPageSize(10);
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

[P2]
미디어 쿼리를 위한 상수 혹은 order를 위한 값 "recent"와 같은 값들은 별도의 constant 파일이나 코드 상단에 선언해서 재사용하는 것이 좋습니다.
유지보수 및 추후 수정이 필요한 경우 한번에 변경할 수 있어 용이합니다.

Comment on lines +68 to +82
function ProductListItem({ item }) {
return (
<div>
<img className="img" src={item.images} alt={item.name} />
<div className="description">
<div className="name">{item.name}</div>
<div className="price">{item.price}원</div>
<div className="favoriteCount">
<img src={unheartIcon} />
{item.favoriteCount}
</div>
</div>
</div>
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

[P1]
별도의 컴포넌트 파일로 분리해주는 것이 좋습니다!
Compound Component 패턴이 아닌 이상 별도의 파일을 통해 관리하는 것이 좋습니다.
https://patterns-dev-kr.github.io/design-patterns/compound-pattern/

return (
<div className="pagination">
<button type="button" className="prevBtn" onClick={handlePrev}>
<img src={arrowLeft} />
Copy link
Collaborator

Choose a reason for hiding this comment

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

[P0]
alt 속성을 추가해주시는 것이 좋습니다!

Comment on lines +10 to +12
if (window.matchMedia("(max-width: 743px)").matches) {
setPageSize(1);
} else if (window.matchMedia("(max-width: 1199px)").matches) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

app.js에 파일에 남겨두었듯이 미디어 쿼리 상수를 constant 파일로 관리하면 가져와서 쓸 수 있습니다.

<main className="bestProduct">
<div className="title">베스트 상품</div>
<div className="list">
{bestItems.map((item) => (
Copy link
Collaborator

Choose a reason for hiding this comment

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

[P1]
bestItems의 배열 길이가 0일때를 생각해서 예외처리를 해주면 좋을 것 같습니다. 항상 배열 값들은 예외처리를 생각하고 empty view를 구현하는 것이 좋습니다.

Comment on lines +11 to +20
if (page) url.searchParams.append("page", page);
if (pageSize) url.searchParams.append("pageSize", pageSize);
if (keyword) url.searchParams.append("keyword", keyword);
url.searchParams.append("orderBy", orderBy);
const res = await fetch(url);
if (!res.ok) {
console.error(res.message);
}
const body = await res.json();
return body;
Copy link
Collaborator

Choose a reason for hiding this comment

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

[P2]
지극히 개인적인 부분입니다만 1 line으로 구성된 if 문들과 각 구문들이 줄바꿈이 잘 되어있지 않아 코드 가독성이 떨어지는 것 같습니다.
lint 나 prettier등을 통해 자동으로 포맷팅되도록 해보면 좋을 것 같습니다!

Copy link
Collaborator

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

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

추가적으로 create-react-app이 공식적으로 지원이 중단될 예정입니다.
리액트 프로젝트를 시작하실 때 vite 혹은 다른 프레임워크를 사용해서 시작하는 방법에 대해서도 찾아보시면 좋을 것 같아요!
관련 글 https://react.dev/blog/2025/02/14/sunsetting-create-react-app

@reach0908
Copy link
Collaborator

  • 로컬에서는 검색기능이 작동하는데 배포링크에서는 안되는 것 같은데 확인 부탁드립니다..
    -> 배포 링크에서도 되는 부분 확인하였습니다.

css를 적용하면서 이미지가 없는 상품 데이터들이 포함된 리스트는 자꾸 비율이 이상하게 돼서 max, min 높이와 넓이를 모두 적용시켰는데 저런 상황에서도 일반적으로 width, height를 적용할 수 있는 좋은 방법이 있을까요?
-> 이런 경우에는 정해진 width height에 맞는 empty image를 하나 만들어서 빈 이미지를 넣어주면 좋습니다!

디자인 패턴을 공부해야 할 것 같은데 컴포넌트를 저정도로만 나눠도 되는지 아니면 버튼이나 검색창 등 더 세분화해서 컴포넌트를 분리하는 것이 좋은지 잘 모르겠습니다.
-> 이 부분은 코드를 작성하시다보면서 감을 잡으시는게 좋습니다. 디자인패턴들에 대해서도 공부해보신다음 여러 패턴을 각 요구사항에 맞게 구현하시다보면 자연스레 각 상황에 어떤 정도가 적당한지에 대해서 감이 잡히실거에요. 결국 많이 만들어보고 시도해보는 것이 좋습니다. 이번에는 이정도로 구현해보셨으니 다음 번에는 리팩토링을 통해 세분화해보는 경험을 해보는 것이 좋겠네요.

저 같은 경우에는 컴포넌트를 우선 구현한다음 한 컴포넌트에 너무 많은 기능들이 들어가 있거나 코드의 가독성이 떨어지거나 앞으로 추가적인 기능적 요구사항이 들어올 경우에 리팩토링을 통해 컴포넌트를 다듬는 방식으로 개발을 주로 합니다.

페이지네이션을 더 효율적으로 구현할 수 있는 방법이 있는지 궁금합니다.
-> 기본적으로 페이지네이션은 백엔드에서도 페이지네이션을 지원해주면 조금 더 효율적으로 관리할 수 있는 부분이 생깁니다. 결국 프론트에서 데이터를 다 받아온 뒤에 페이지네이션을 해주는 것은 반쪽짜리 이므로 추후 백엔드 고도화가 된다면 해당 부분을 공부해보시면 좋을 것 같습니다.

지금 있는 코드에서 최대한 개선을 해본다면 기본적으로 페이지의 값을 관리하는 하나의 함수로 정리할 수 있을 것 같습니다. 모든 값들이 currentPage에 종속되어있기 때문에 아래와 같이 개선해보고 useMemo, useCallback과 같은 메모이제이션을 통해 성능 개선을 노려볼 수 있습니다.

 const paginationData = useMemo(() => {
    const totalPages = Math.ceil(totalCount / limit);
    
    // 현재 페이지가 속한 그룹 계산
    const currentGroup = Math.ceil(currentPage / offset);
    const startPage = (currentGroup - 1) * offset + 1;
    const endPage = Math.min(startPage + offset - 1, totalPages);
    
    // 페이지 번호 배열 생성
    const pages = [];
    for (let i = startPage; i <= endPage; i++) {
      pages.push(i);
    }
    
    return {
      totalPages,
      startPage,
      endPage,
      pages,
      hasPrev: currentPage > 1,
      hasNext: currentPage < totalPages
    };
  }, [currentPage, totalCount, limit, offset]);
  
  const handlePrev = () => {
    if (currentPage > 1) {
      setCurrentPage(currentPage - 1);
    }
  };
  
  const handleNext = () => {
    if (currentPage < paginationData.totalPages) {
      setCurrentPage(currentPage + 1);
    }
  };

paginationData로 부터 받은 값들을 적절히 사용하여 컴포넌트를 구성하면 될 것 같습니다.

@reach0908 reach0908 merged commit 32be7e8 into codeit-sprint-fullstack:react-오하영 Mar 12, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants