-
Notifications
You must be signed in to change notification settings - Fork 26
[김참솔] Sprint 5 #92
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 #92
The head ref may contain hidden characters: "React-\uAE40\uCC38\uC194-sprint5"
Conversation
- `useMediaQuery` hook - 이름 변경 : `useMediaQuery` -> `useDevice` - `change` event handler 동작 방식 개선 - Component가 과도하게 re-rendering 되는 문제 수정 - `fetchProducts` 서버 요청이 과도하게 전송되는 문제 수정
|
스프리트 미션 하시느라 수고 많으셨어요. |
이번 미션에서는 강의에서 사용하던 폴더 구조를 그대로 따라했는데요. Component와 파일 갯수가 많아지면 원하는 파일을 찾아다니기 어렵겠다는 생각이 들었습니다. 일반적으로 React 프로젝트는 폴더 구조를 어떻게 잡나요?"일반적으로"라면 참솔님께서 작성하신 구조가 일반적인 구조라고 볼 수 있을 것 같아요. 다음 문서 참고하시면 도움 되실거예요 😊 |
React 코드에서 반응형 디자인을 구현하는 방법이 궁금합니다. 이번 미션에서는 useMediaQuery라는 custom hook을 만들어서 사용했는데요. 더 좋은 방법이 있을까요? 현업에서는 어떤 방식으로 반응형 디자인을 구현하나요?자바스크립트로 DOM에 접근하여 처리하면 성능이 좋지 않기에 왠만하면 |
| @@ -0,0 +1,28 @@ | |||
| const BASE_URL = "https://panda-market-api.vercel.app"; | |||
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.
base URL은 환경 변수에 저장하시는게 좋습니다!
환경 변수(Environment Variable):
process.env에 내장되며 앱이 실행될 때 적용할 수 있는 값입니다!
다음과 같이 적용할 수 있습니다:
// .env.development
REACT_APP_BASE_URL="http://localhost:3000"
// .env.production
REACT_APP_BASE_URL="http://myapi.com"
// 사용시
<a href={`${process.env.REACT_APP_BASE_URL}/myroute`}>URL</a>
왜 환경 변수에 저장해야 하나요?
개발(dev), 테스트(test), 실제 사용(prod) 등 다양한 환경에서 앱을 운영하게 되는 경우, 각 환경에 따라 다른 base URL을 사용해야 할 수 있습니다. 만약 코드 내에 하드코딩되어 있다면, 각 환경에 맞춰 앱을 배포할 때마다 코드를 변경해야 하며, 이는 매우 번거로운 작업이 됩니다. 하지만, 환경 변수를 .env.production, .env.development, .env.test와 같이 설정해두었다면, 코드에서는 단지 다음과 같이 적용하기만 하면 됩니다.
const apiUrl = `${process.env.REACT_APP_BASE_URL}/api`;
이러한 방식으로 환경 변수를 사용하면, 배포 환경에 따라 쉽게 URL을 변경할 수 있으며, 코드의 가독성과 유지보수성도 개선됩니다.
실제 코드 응용과 관련해서는 다음 한글 아티클을 참고해보세요! => 보러가기
| export async function fetchProducts({ | ||
| keyword = "", | ||
| page = 1, | ||
| pageSize = 10, | ||
| orderBy = "recent", | ||
| } = {}) { |
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.
굿굿 ! 옵셔널한 쿼리들을 객체 인자로 받고 있군요 ! 👍
객체로 만들어 두니 사용하기 정말 편하겠어요 👍👍
| const url = new URL(`${BASE_URL}/products`); | ||
| url.searchParams.append("page", page); | ||
| url.searchParams.append("pageSize", pageSize); | ||
| url.searchParams.append("orderBy", orderBy); | ||
| if (keyword) { | ||
| url.searchParams.append("keyword", keyword); | ||
| } |
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.
크으 ~ 좋습니다. URL 객체를 사용하셨군요. searchParams에 추가 하신 점도 좋습니다 👍👍
URL 인코딩을 자동으로 처리하여 특수 문자나 공백이 포함된 값에서도 안전하게 동작되겠네요 ~!
| if (!response.ok) { | ||
| throw new Error("Failed to fetch products"); | ||
| } |
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.
크으~ 예외처리도 처리하셨군요 꼼꼼합니다 !
| import "./Item.css"; | ||
|
|
||
| function Item({ imageUrl, title, price, likeCount }) { | ||
| const priceString = Intl.NumberFormat().format(price) + "원"; |
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.
국제화 내장 모듈인 Intl을 사용하셨군요 ! 👍
정말 꼼꼼하시네요 참솔님 ㄷㄷㄷ
| <img src={imageUrl} alt="상품 이미지" /> | ||
| </div> | ||
| <div className="Item-info"> | ||
| <p className="Item-title">{title}</p> |
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.
해당 태그는 "단락"(p) 보다는 "제목"(h)이라는 의미에 가깝겠군요? 😊
| ); | ||
| } | ||
|
|
||
| export { ItemsSection, ItemsSectionContent, ItemsSectionHeader }; |
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.
크으.. 컴포넌트를 분할하여 정의하셨군요 정말 깔끔하네요 👍
| function ItemsSection({ spacing = 16, children }) { | ||
| return ( | ||
| <section className="ItemsSection" style={{ gap: spacing }}> | ||
| {children} | ||
| </section> | ||
| ); | ||
| } |
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.
(선택)다음과 같이 스타일을 정의하면 더욱 유연한 스타일을 지정할 수 있습니다 !
| function ItemsSection({ spacing = 16, children }) { | |
| return ( | |
| <section className="ItemsSection" style={{ gap: spacing }}> | |
| {children} | |
| </section> | |
| ); | |
| } | |
| function ItemsSection({ style = { gap: 16 }, children }) { | |
| return ( | |
| <section className="ItemsSection" style={{ ...style, /* `ItemsSection`의 고유 스타일 */ }}> | |
| {children} | |
| </section> | |
| ); | |
| } |
| const matchesDesktop = _matchesDesktop ?? matchMedia("(min-width: 1200px)"); | ||
| const matchesTablet = | ||
| _matchesTablet ?? matchMedia("(min-width: 768px) and (max-width: 1199px)"); | ||
| const matchesMobile = _matchesMobile ?? matchMedia("(max-width: 767px)"); | ||
|
|
||
| const [deviceInfo, setDeviceInfo] = useState({ | ||
| isDesktop: matchesDesktop.matches, | ||
| isTablet: matchesTablet.matches, | ||
| isMobile: matchesMobile.matches, | ||
| }); |
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.
크으. 적절한 커스텀 훅이네요.
요구사항에 적합한 적절한 커스텀 훅입니다 !
질의에 대해서는 css로 처리하는게 좋다고 답하였으나
요구사항 대로라면 디바이스 별로 데이터 출력 수가 달라야 하는 것을 보니 js가 들어갈 수밖에 없겠네요.
이러한 상황에서 훅을 만드시면 재사용성과 유지보수성이 크게 증가할 것으로 보여요 👍👍
| const bestProducts = [...products] | ||
| .sort((a, b) => b.favoriteCount - a.favoriteCount) | ||
| .slice(0, bestProductsColumns); |
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.
(선택) 해당 로직은 정렬이 들어가기에 메모이제이션을 고려해볼 수 있겠네요 !
| const bestProducts = [...products] | |
| .sort((a, b) => b.favoriteCount - a.favoriteCount) | |
| .slice(0, bestProductsColumns); | |
| const bestProducts = useMemo( () => [...products] | |
| .sort((a, b) => b.favoriteCount - a.favoriteCount) | |
| .slice(0, bestProductsColumns), [products]); |
|
크으 참솔님~ 미션 수행하시느라 정말 수고 많으셨습니다 !! 👍👍👍 |
요구사항
기본
중고마켓
중고마켓 반응형
심화
주요 변경사항
/itemspath로 이동하면 상품 목록을 불러오고 '베스트 상품' 및 '전체 상품' section에 상품을 표시합니다.스크린샷
멘토에게
npm run build후distdirectory에 있는 파일들을 모두 업로드해도 웹사이트가 정상적으로 표시되지 않고 있습니다. 다른 작업이 더 필요한걸까요?useMediaQuery라는 custom hook을 만들어서 사용했는데요. 더 좋은 방법이 있을까요? 현업에서는 어떤 방식으로 반응형 디자인을 구현하나요?