-
Notifications
You must be signed in to change notification settings - Fork 42
[남만재] sprint5 #218
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
base: React-남만재
Are you sure you want to change the base?
The head ref may contain hidden characters: "React-\uB0A8\uB9CC\uC7AC-sprint5"
[남만재] sprint5 #218
Conversation
style: ProductList 스타일 추가
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.
수고하셨습니다!
첫 리액트 미션인데, 미션 구현도 어렵지않게 하시고 깔끔하게 잘 짜시네요 👍
리팩토링 해보시면서 최적화나 유지보수를 고려해 개발해보는 시야도 넓혀봅시다! ㅎㅎ
주요 리뷰 포인트
- App 컴포넌트에서 관심사 분리가 필요한 이유
- deps list 최적화로 불필요한 effect 실행 방지하기
- 구조분해할당 (destruction)으로 코드 간소화하기
- 직관적인 네이밍 사용하기, 재사용될 수 있는 로직은 커스텀 훅으로 분리하기
- svg 컴포넌트화해서 property 자유자재로 다루기
- resize 이벤트 debounce 적용해 최적화하기
useEffect(() => { | ||
if (width < BREAK_POINT.md) { | ||
setBestPageSize(1); | ||
setPageSize(4); | ||
} else if (width < BREAK_POINT.lg) { | ||
setBestPageSize(2); | ||
setPageSize(6); | ||
} else { | ||
setBestPageSize(4); | ||
setPageSize(10); | ||
} | ||
}, [width]); |
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.
deps list에 있는 width가 바뀔때마다 effect가 실행되게하기보다는,
브레이크포인트별 md, lg 값이 true인지 아닌지(deviceType 계산) 먼저 계산한 후,
실제 이 값이 바뀔때에만 effect가 실행될수있도록 만들어주는편이 불필요한 작업을 줄일 수 있겠네요 :) 지금 구조대로라면 width가 1px이라도 바뀐다면 effect가 실행될테니까요!
이렇게 개선해보는건 어떨까요?
// 디바이스 타입 계산
const getDeviceType = (width) => {
if (width < BREAK_POINT.md) return 'mobile';
if (width < BREAK_POINT.lg) return 'tablet';
return 'desktop';
};
const deviceType = getDeviceType(width);
useEffect(() => {
if (deviceType === 'mobile') {
setBestPageSize(1);
setPageSize(4);
} else if (deviceType === 'tablet') {
setBestPageSize(2);
setPageSize(6);
} else {
setBestPageSize(4);
setPageSize(10);
}
}, [deviceType]);
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.
getDeviceType
함수 추가해, width를 인자로 받아서 'mobile', 'tablet', 'desktop' 중 하나를 반환하는 함수를 만들고 이 함수를 사용해 deviceType
의 값을 계산합니다.
그리고 useEffect 의존성 배열을 deviceType으로 변경해주면, 실제로 디바이스 타입이 바뀔 때만 effect가 실행되도록 개선해보는 코드인데요!
이렇게 해주면 width가 바뀌어도 같은 디바이스 타입 범위 내에서는 불필요한 effect 실행을 방지할 수 있고, 디바이스 타입이 좀 더 명확하게 구분되어 코드의 의도를 더 명확하게 드러낼 수 있어 로직 분리 및 유지보수도 쉬워지겠죠? :)
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.
안그래도 1px 마다 리렌더링 되는것에 있어서 고민이 있었는데 마음이 급해서 깊게 생각하지 못했네요,, 이렇게 단순 명료한 방법이 있었다니...감사합니다~!
const result = await getBestProducts(); | ||
setBestProducts(result.list); |
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 result = await getBestProducts(); | |
setBestProducts(result.list); | |
const { list } = await getBestProducts(); | |
setBestProducts(list); |
이렇게 구조분해할당을 활용하시면 좀더 간소화되겠네요!
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.
그럼 밑에 전체상품 fetch 함수도 try...catch 문으로 수정해서 구조분해 할당으로 list와 totalCount를 받아와서 사용하는게 더 나은 방법일까요?
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.
ㅎㅎ 나중에 타입스크립트를 사용하게되면 조금 상황이 달라질거예요.
setBestProducts에 담기는 데이터의 타입이 undefined인 경우도 허용이 된다면 구조분해할당으로 바로 넣으셔도되고,
허용하고싶지않다면 optional chaining을 활용하기 위해 setBestProducts(result?.list);
로 유지해주시는게 좋습니다 :)
const [products, setProducts] = useState([]); | ||
const [bestProducts, setBestProducts] = useState([]); | ||
const [bestPageSize, setBestPageSize] = useState(1); | ||
const [page, setPage] = useState(1); | ||
const [pageSize, setPageSize] = useState(4); | ||
const [orderBy, setOrderBy] = useState("recent"); | ||
const [keyword, setKeyword] = useState(""); | ||
const [totalCount, setTotalCount] = useState(); |
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.
App.js파일에서 data fetching 및 state & effect 관리를 하고계시네요 :)
해당 컴포넌트는 리턴문에서 리턴되는 모든 컴포넌트를 렌더링할때 쓰입니다.
즉, 이 컴포넌트에서 data fetching을 시도하게되면 해당 자원이 필요없는 컴포넌트에서는 필요하지도 않은 자원이 요청되고, 초기 로딩이 지연되는 상황이 발생하겠죠?
따라서 해당 data fetching 로직은 필요한 컴포넌트에서만 (Products 컴포넌트에서) 직접 데이터를 가져오도록 옮기시는게 더 나은 구조를 위한 관심사 분리 원칙에서도, 성능을 위해서도 좋은 선택입니다.
state 관리 & data fetching 로직을 관련된 컴포넌트에서 직접 하도록 옮겨봅시다!
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.
pr 후에 발견해서 Items 페이지 컴포넌트로 변경해주었습니다!
const totalPages = Math.ceil(totalCount / pageSize); | ||
const groupSize = 5; | ||
const currentGroup = Math.floor((page - 1) / groupSize); | ||
const startPage = currentGroup * groupSize + 1; | ||
const endPage = Math.min(startPage + groupSize - 1, totalPages); | ||
|
||
const handlePageClick = (newPage) => { | ||
if (newPage >= 1 && newPage <= totalPages) { | ||
setPage(newPage); | ||
} | ||
}; | ||
|
||
const pageNums = []; | ||
for (let i = startPage; i <= endPage; i++) { | ||
pageNums.push(i); | ||
} |
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.
src > components 아래 있는 컴포넌트들은 보통 공용 컴포넌트 역할을 하는데요!
해당 로직은 페이지네이션을 위한 로직처럼 보여요.
만약 페이지네이션 버튼을 재사용할 목적이었다면, 재사용될수있는 로직 (페이지네이션 기능 부분)을 커스텀훅으로 분리해주는게 좀더 재사용성을 높이면서도 코드 중복을 줄여줄 수 있는 좋은 방법이 될 수있고, 해당 컴포넌트의 이름 또한 PaginationButton
과 같이 이름만 봐도 역할을 알 수 있는 직관적인 네이밍으로 변경해주는게 좋을것같네요 :)
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.
폴더 구조에 대한 이해가 부족한 듯 합니다... 알려주셔서 감사합니다!
그럼 커스텀 훅으로 리팩토링 한다 하였을 때 productList 컴포넌트에서 해당 훅을 사용한는 것이 일반적일까요?
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.
네, 커스텀 훅으로 페이지네이션 로직을 리팩토링 한다 가정한다면 필요한 컴포넌트에서 직접 훅을 호출하시면됩니다.
페이지네이션 로직이 필요한 UI가 있겠죠?
useEffect(() => { | ||
const onClickOutside = (e) => { | ||
if (containerRef.current && !containerRef.current.contains(e.target)) { | ||
setIsOpen(false); | ||
} | ||
}; | ||
document.addEventListener("mousedown", onClickOutside); | ||
return () => document.removeEventListener("mousedown", onClickOutside); | ||
}, []); |
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.
이렇게 outside click을 감지해 isOpen state을 처리하는 로직도 재사용 가능성이 있으니 커스텀훅으로 분리해보는건 어떨까요? :)
useEffect(() => { | ||
if (width > BREAK_POINT.md - 1) { | ||
setNavLogo(iconLogo); | ||
} | ||
if (width < BREAK_POINT.md) { | ||
setNavLogo(logo); | ||
} | ||
}, [width]); |
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.
아까와 비슷하게 width를 deps list로 사용하고있는 문제가 발생했고, 코드 구조도 비슷하네요! 반복되는 코드들이 있고, 값을 사용하는 패턴도 동일하다면 아예 커스텀 훅으로 만들어보는게 좋을것같은데요? :)
- useResponsiveValue
import { useMemo } from "react";
import useViewportWidth from "./useViewportWidth";
export default function useResponsiveValue(breakpoint, desktopValue, mobileValue) {
const width = useViewportWidth();
const value = useMemo(() => {
return width >= breakpoint ? desktopValue : mobileValue;
}, [width, breakpoint, desktopValue, mobileValue]);
return value;
}
- 사용할 때
const navLogo = useResponsiveValue(BREAK_POINT.md, iconLogo, logo);
이렇게해주면 불필요한 effect 실행도 줄이면서 코드 중복을 효율적으로 줄일 수 있겠네요 :)
불필요한 effect 줄이기에 대한 더 자세한 예시는 아래 아티클 참고해보세요!
참고
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.
오 그럼 단순 반응형만을 위해 만든훅이고 useViewportWidth 훅은 따로 개별 사용될 여지가 없을거라 생각되는데 그렇다면 이걸 하나의 훅으로 묶어버리는게 더 나을수도 있겠네요 ㅎㅎ 감사합니다!
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.
앗 제가 로직을 잘못 읽었군요 이렇게 사용하면 반응형으로 이미지 로드하는 방식을 더 간소화 할 수 있겠네요 감사합니다!
/> | ||
<div>{product.name}</div> | ||
<div css={productPrice}>{product.price.toLocaleString() + "원"}</div> | ||
<div css={productCount}>{`♡ ${product.favoriteCount}`}</div> |
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.
ㅎㅎㅎ 나중에 like 기능도 붙여야하니까 svg 아이콘으로 미리 바꿔봅시다!
이 경우에는 인터렉션을 처리하며 like버튼이 채워지거나 색깔이 바뀌는등 property를 활용해야하니 컴포넌트화하는게 좋을것같아요.
관련해서 아티클 참고해보시면 도움되실것같네요!
참고
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.
resize 이벤트가 너무 빈번하게 일어날수있으니, debounce를 적용해서 최적화해보는건 어떨까요?
참고
질문에 대한 답변
최대 너비를 고정값으로 고정하게되면 뷰포트가 늘어나도 너비가 고정되어 자연스럽게 늘어나지않습니다. width: 100%;
max-width: 344px; 그리고, 요소가 남은 공간을 모두 차지하고싶게 만들고싶다면 flex:1; 을 쓰거나, 지정된 비율로 차지하게 만들고싶다면 flex-basis 혹은 %를 사용해 주시면 좀 더 유연하게 설정이 될거예요 :)
|
요구사항
기본
Desktop : 4개 보이기
Tablet : 2개 보이기
Mobile : 1개 보이기
Desktop : 10개 보이기
Tablet : 6개 보이기
Mobile : 4개 보이기
심화
주요 변경사항
스크린샷
좋아요순
검색 좋아요순
검색 최신순
멘토에게