-
Notifications
You must be signed in to change notification settings - Fork 26
[김준열] Sprint5 #133
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
[김준열] Sprint5 #133
The head ref may contain hidden characters: "React-\uAE40\uC900\uC5F4-sprint5"
Conversation
|
스프리트 미션 하시느라 수고 많으셨어요. |
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.
벡터 이미지는 svg로 저장하시는걸 추천드려요 !
이하 MDN
SVG(Scalable Vector Graphics)는 2차원 벡터 그래픽을 서술하는XML기반의 마크업 언어입니다.SVG는 텍스트 기반의 열린 웹 표준 중 하나로, 모든 사이즈에서 깔끔하게 렌더링 되는 이미지를 서술하며 CSS, DOM, JavaScript, SMIL (en-US) 등 다른 웹 표준과도 잘 동작하도록 설계됐습니다. SVG는 달리 말하자면 HTML과 텍스트의 관계를 그래픽에 적용한 것입니다
이미지 파일은 최소 단위가 픽셀로 되어있으며 확대했을 때 이미지가 깨질 수 있어요 !
피그마에서 export하실 때에 svg로 export할 수 있습니다 😊
| const apiClient = axios.create({ | ||
| baseURL: import.meta.env.VITE_API_BASE_URL, | ||
| timeout: 10000, | ||
| }); |
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.
굿굿 ~ axios를 사용하셨군요 !
훌륭합니다 ! 안그래도 추천드리려 했는데 ㅎㅎㅎ
환경 변수도 잘 적용하셨네요 👍👍👍
| return response; | ||
| }, | ||
| (error) => { | ||
| console.error("API Error:", error.response?.status, error.message); |
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.
오호. 디버깅용으로 로깅하셨나보군요?
라이브러리를 이것저것 활용해보려는 시도가 너무 좋군요 !
인터셉터를 활용하여 로깅도 해보시고 👍👍
| // api/products/getProducts.js | ||
| import apiClient from "../axiosInstance"; | ||
|
|
||
| export const getProducts = async (params = {}) => { |
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.
파라메터가 비어있으므로 어떤 파라메터가 필요한지 유추할 수 어려울 수 있겠어요 🤔
다음과 같이 추가해볼 수 있습니다 !
| export const getProducts = async (params = {}) => { | |
| export const getProducts = async ({ | |
| page = 1, | |
| pageSize = 10, | |
| orderBy = "recent", | |
| keyword = "", | |
| } = {}) => { |
(더 나아가서) 주석으로 명시하는 방법도 있습니다 !
다음과 같이 작성해볼 수 있어요:
/**
* 상품 목록을 가져오는 API
*
* @param {Object} options - 요청 옵션
* @param {number} [options.page=1] - 페이지 번호
* @param {number} [options.pageSize=10] - 한 페이지에 보여줄 아이템 수
* @param {"recent"|"favorite"} [options.orderBy="recent"] - 정렬 기준
* @param {string} [options.keyword] - 검색 키워드
* @returns {Promise<Object>} - 상품 목록 데이터
* @throws {Error} API 호출 실패 시 에러 메시지
*
* @example
* // 일반 상품 조회
* const products = await getProducts({ page: 2, pageSize: 20 });
*
* @example
* // 베스트 상품 조회
* const bestProducts = await getProducts({ page: 1, pageSize: 4, orderBy: "favorite" });
*/
export const getProducts = async ({
page = 1,
pageSize = 10,
orderBy = "recent",
keyword,
} = {}) => {만약 타입스크립트를 사용한다면 타입만 명시해주면 되겠지만, 지금은 자바스크립트이기에 위와 같이 jsdoc을 통해서 주석을 추가해볼 수 있습니다 😉
| params: { | ||
| page: params.page || 1, | ||
| pageSize: params.pageSize || 10, | ||
| orderBy: params.orderBy || "recent", | ||
| ...(params.keyword && { keyword: params.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.
(이어서) 방금 리뷰한 것을 반영한다면 기본값을 설정할 필요가 없겠군요 !
| params: { | |
| page: params.page || 1, | |
| pageSize: params.pageSize || 10, | |
| orderBy: params.orderBy || "recent", | |
| ...(params.keyword && { keyword: params.keyword }), | |
| }, | |
| params: { | |
| page, | |
| pageSize, | |
| orderBy, | |
| keyword, | |
| }, |
| } catch (error) { | ||
| throw new Error( | ||
| error.response?.data?.message || | ||
| error.message || | ||
| "상품을 불러오는데 실패했습니다." | ||
| ); | ||
| } |
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.
조건문이 복잡한 경우 별칭을 사용해볼 수도 있어요 😉
| } catch (error) { | |
| throw new Error( | |
| error.response?.data?.message || | |
| error.message || | |
| "상품을 불러오는데 실패했습니다." | |
| ); | |
| } | |
| } catch (error) { | |
| const hasMessage = error.response?.data?.message || error.message; | |
| const fallbackMessage = "상품을 불러오는데 실패했습니다."; | |
| throw new Error(hasMessage || fallbackMessage); | |
| } |
논리 연산자가 2 개 이상 들어가면 가독성이 저하될 수 있으므로 위와 같이 별칭(hasMessage)을 정의해볼 수 있습니다 😉
| export const getBestProducts = async (limit = 4) => { | ||
| try { | ||
| const response = await apiClient.get("/products", { | ||
| params: { | ||
| page: 1, | ||
| pageSize: limit, | ||
| orderBy: "favorite", | ||
| }, | ||
| }); | ||
| return response.data; | ||
| } catch (error) { | ||
| throw new Error( | ||
| error.response?.data?.message || | ||
| error.message || | ||
| "베스트 상품을 불러오는데 실패했습니다." | ||
| ); | ||
| } | ||
| }; |
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.
(이어서) 해당 함수는 이제 필요 없겠군요 !
| export const getBestProducts = async (limit = 4) => { | |
| try { | |
| const response = await apiClient.get("/products", { | |
| params: { | |
| page: 1, | |
| pageSize: limit, | |
| orderBy: "favorite", | |
| }, | |
| }); | |
| return response.data; | |
| } catch (error) { | |
| throw new Error( | |
| error.response?.data?.message || | |
| error.message || | |
| "베스트 상품을 불러오는데 실패했습니다." | |
| ); | |
| } | |
| }; |
getProducts에서 orderBy도 고려하므로 다음과 같이 호출하면 현재 함수와 같은 기능을 수행하겠어요 😊:
const bestProducts = await getProducts({
page: 1,
pageSize: 4,
orderBy: "favorite",
});| export const searchProducts = async (keyword, params = {}) => { | ||
| try { | ||
| if (!keyword || !keyword.trim()) { | ||
| throw new Error("검색어를 입력해주세요."); | ||
| } | ||
|
|
||
| const response = await apiClient.get("/products", { | ||
| params: { | ||
| page: params.page || 1, | ||
| pageSize: params.pageSize || 10, | ||
| orderBy: params.orderBy || "recent", | ||
| keyword: keyword.trim(), | ||
| }, | ||
| }); | ||
| return response.data; | ||
| } catch (error) { | ||
| throw new Error( | ||
| error.response?.data?.message || | ||
| error.message || | ||
| "상품 검색에 실패했습니다." | ||
| ); | ||
| } | ||
| }; |
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.
해당 함수도 제안해드린 getProducts를 사용해볼 수 있겠군요 😉
| const useResponsiveCount = () => { | ||
| const [screenSize, setScreenSize] = useState("desktop"); | ||
|
|
||
| useEffect(() => { | ||
| const updateScreenSize = () => { | ||
| const width = window.innerWidth; | ||
|
|
||
| if (width < 768) { | ||
| setScreenSize("mobile"); | ||
| } else if (width < 1024) { | ||
| setScreenSize("tablet"); | ||
| } else { | ||
| setScreenSize("desktop"); | ||
| } | ||
| }; | ||
|
|
||
| // 초기 설정 | ||
| updateScreenSize(); | ||
|
|
||
| // 윈도우 리사이즈 이벤트 리스너 | ||
| window.addEventListener("resize", updateScreenSize); | ||
|
|
||
| // 클린업 | ||
| return () => window.removeEventListener("resize", updateScreenSize); | ||
| }, []); | ||
|
|
||
| // 베스트 상품 개수 | ||
| const getBestProductCount = () => { | ||
| switch (screenSize) { | ||
| case "mobile": | ||
| return 1; // 모바일: 1개 | ||
| case "tablet": | ||
| return 2; // 태블릿: 2개 | ||
| case "desktop": | ||
| return 4; // 데스크톱: 4개 | ||
| default: | ||
| return 4; | ||
| } | ||
| }; | ||
|
|
||
| // 전체 상품 개수 (한 페이지당) | ||
| const getAllProductCount = () => { | ||
| switch (screenSize) { |
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 useDevice = () => {
const [device, setDevice] = useState("desktop");
useEffect(() => {
const updateDevice = () => {
const width = window.innerWidth;
if (width < 768) {
setDevice("mobile");
} else if (width < 1024) {
setDevice("tablet");
} else {
setDevice("desktop");
}
};
updateDevice();
window.addEventListener("resize", updateDevice);
return () => window.removeEventListener("resize", updateDevice);
}, []);
return device; // "mobile" | "tablet" | "desktop"
};
export default useDevice;|
|
||
| function App() { | ||
| // 상태 관리 | ||
| const [sortBy, setSortBy] = useState("latest"); | ||
| const [searchQuery, setSearchQuery] = useState(""); | ||
| const [currentPage, setCurrentPage] = useState(1); | ||
| const [totalCount, setTotalCount] = useState(0); | ||
|
|
||
| // 반응형 훅 사용 | ||
| const { getAllProductCount, screenSize } = useResponsiveCount(); | ||
| const pageSize = getAllProductCount(); | ||
|
|
||
| useEffect(() => { | ||
| setCurrentPage(1); | ||
| }, [screenSize]); |
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 App() { | |
| // 상태 관리 | |
| const [sortBy, setSortBy] = useState("latest"); | |
| const [searchQuery, setSearchQuery] = useState(""); | |
| const [currentPage, setCurrentPage] = useState(1); | |
| const [totalCount, setTotalCount] = useState(0); | |
| // 반응형 훅 사용 | |
| const { getAllProductCount, screenSize } = useResponsiveCount(); | |
| const pageSize = getAllProductCount(); | |
| useEffect(() => { | |
| setCurrentPage(1); | |
| }, [screenSize]); | |
| export const ALL_PRODUCT_COUNT = { | |
| mobile: 4, | |
| tablet: 6, | |
| desktop: 10, | |
| }; | |
| function App() { | |
| // 상태 관리 | |
| const [sortBy, setSortBy] = useState("latest"); | |
| const [searchQuery, setSearchQuery] = useState(""); | |
| const [currentPage, setCurrentPage] = useState(1); | |
| const [totalCount, setTotalCount] = useState(0); | |
| // 반응형 훅 사용 | |
| const device = useDevice(); | |
| const pageSize = ALL_PRODUCT_COUNT[device]; | |
| useEffect(() => { | |
| setCurrentPage(1); | |
| }, [device]); |
자. 이렇게 변경하면 useDevice는 이제 device에 대한 상태만 반환하므로 더욱 다양하게 활용할 수 있을거예요.
처음에 말씀드렸던 "유저 맞춤 추천 상품"등과 같은 개념들이 생겨나도 거뜬하겠죠? 💪
|
준열님 ~~~ 정말 오랜만이예요 ㅎㅎㅎ 궁금하신거 있으시면 편하게 DM주세요 준열님 ! |
배포
배포
요구사항
기본
중고마켓
중고마켓 반응형
베스트 상품
전체 상품
심화
주요 변경사항
스크린샷
멘토에게