-
Notifications
You must be signed in to change notification settings - Fork 26
[유인화] sprint5 #109
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 #109
The head ref may contain hidden characters: "React-\uC720\uC778\uD654-sprint5"
Conversation
|
스프리트 미션 하시느라 수고 많으셨어요. |
지금 상황에서 버튼을 따로 컴포넌트로 만드는 건 비효율적인가요?버튼 컴포넌트를 따로 만드는건 흔한 방법입니다 ! 만약 안만들어두셨다면 버튼 컴포넌트를 만드시는게 좋겠네요 😉 |
폴더랑 파일이 많아지면서 조금씩 헷갈리고 있는데 혹시 이럴 때 팁이나 좋은 정리 방법이 있을까요?폴더 구조는 정말 다양합니다 ! 다음 문서 참조하시면 도움이 될 것 같네요 😉 |
|
피그마 디자인을 보면 모바일 뷰어에서 다음과 같은 상황으로 보입니다 ! : Mobile:이 때, 다음과 같이 작성해볼 수 있습니다 !: const Layout = styled.header`
display: grid;
grid-template-columns: auto 1fr auto auto;
grid-template-areas:
"header search add dropdown";
gap: 12px;
align-items: center;
@media (max-width: 768px) {
grid-template-columns: 1fr 1fr;
grid-template-areas:
"header add"
"search dropdown";
gap: 8px 10px;
}
`;
const Header = styled.div`grid-area: header;`;
const Search = styled.div`grid-area: search;`;
const AddBtn = styled.div`grid-area: add;`;
const SortDropdown = styled.div`grid-area: dropdown;`; |
| function App() { | ||
| return ( | ||
| <BrowserRouter> | ||
| <Header /> | ||
| <div className="withHeader"> | ||
| <Routes> | ||
| <Route index element={<HomePage />} /> | ||
| <Route path="community" element={<CommunityPage />} /> | ||
| <Route path="items" element={<MarketPage />} /> | ||
| <Route path="profile" element={<ProfilePage />} /> | ||
| <Route path="additem" element={<AddItemPage />} /> | ||
| </Routes> | ||
| </div> | ||
| </BrowserRouter> | ||
| ); | ||
| } |
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.
react-routes-dom에서 레이아웃을 설정할 수 있어요. 😊
Consider: 판다마켓 초기에 작성한 로그인과 회원가입과 같은 페이지는 Header가 없는 디자인이예요. 지금과 같이 한다면 특정 페이지의 레이아웃을 조정하기 어려울거예요 !
Routes에서 element를 지정하여 중첩 레이아웃을 사용할 수 있으니 참고해서 설계해보세요 😊
tl;dr
// App.tsx
<Routes>
<Route path="/" element={<Main />}> // 중첩 라우팅
<Route path="user-management" element={<UserManagement />} />
<Route path="child-management" element={<ChildManagement />} />
</Route>
<Route path="/login" element={<Login />} />
</Routes>
// Main.tsx
<MainWrapper>
<MainMenu />
<Outlet /> // children과 같은 효과 ! ✨
</MainWrapper>| export async function getProducts(params = {}) { | ||
| const query = new URLSearchParams(params).toString(); |
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 함수를 따로 분리하셨군요 ! 👍
좋습니다 ! API 함수가 분리되어 있으니 재사용 및 유지관리가 용이하겠어요 👍
| @@ -0,0 +1,12 @@ | |||
| export async function getProducts(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.
현재 params는 어떤 매개변수를 가질지 모르겠군요 !
다음과 같이 만들어볼 수 있을거예요 😉:
| export async function getProducts(params = {}) { | |
| export async function getProducts({ | |
| page = 1, | |
| pageSize = 10, | |
| orderBy = "recent", | |
| 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.
(더 나아가서)혹은 jsdoc을 활용해볼 수도 있습니다 !
/**
* 상품 목록을 서버에서 가져옵니다.
*
* @param {Object} params - 상품 조회에 필요한 파라미터
* @param {number} [params.page=1] - 조회할 페이지 번호 (기본값: 1)
* @param {number} [params.pageSize=10] - 한 페이지에 표시할 상품 수 (기본값: 10)
* @param {"recent"|"popular"|"price"} [params.orderBy="recent"] - 상품 정렬 기준 (기본값: "recent")
* @param {string} [params.keyword=""] - 검색 키워드 (기본값: 빈 문자열)
* @returns {Promise<{
"totalCount": 0,
"list": [
{
"createdAt": "2025-08-11T07:00:52.930Z",
"favoriteCount": 0,
"ownerNickname": "string",
"ownerId": 1,
"images": [
"https://example.com/..."
],
"tags": [
"전자제품"
],
"price": 0,
"description": "string",
"name": "상품 이름",
"id": 1
}
]
}>} 서버로부터 받은 상품 데이터 객체
*
* @example
* // 최근 등록된 상품 10개를 가져오기
* const products = await getProducts({ page: 1, pageSize: 10, orderBy: "recent" });
*/
export async function getProducts({
page = 1,
pageSize = 10,
orderBy = "recent",
keyword = "",
}) {
// ...
}그래서 JSDoc이 뭔가요?
JSDoc은 다음과 같이 작성할 수 있어요:
/**
* SMS를 전송합니다.
* @param {string} title - SMS 제목 (선택 사항)
* @param {string} text - SMS 내용
* @param {string} to - 수신자 전화번호
* @returns {Promise} SMS 전송 결과를 포함한 객체
*/
sendSMS(title, text, to);이렇게 하면 Visual Studio Code에서 해당 함수를 호출할 때 매개변수가 어떤 것이 있는지도 볼 수 있으며 마우스를 호버하거나 함수가 포커스 되었을 때 JSDoc을 볼 수도 있습니다 😊
개발자 경험이 많이 좋아지겠죠?
| const query = new URLSearchParams(params).toString(); | ||
|
|
||
| const response = await fetch( | ||
| `https://panda-market-api.vercel.app/products?${query}` |
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.
https://panda-market-api.vercel.app는 base url로 따로 관리해볼 수 있겠네요 !
| `https://panda-market-api.vercel.app/products?${query}` | |
| const BASE_URL = 'https://panda-market-api.vercel.app'; | |
| // ... | |
| `${baseUrl}/products?${query}` |
하지만 위 방법 보다는 환경 변수를 사용하시는걸 추천드립니다 !
환경 변수(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 getProducts(params = {}) { | ||
| const query = new URLSearchParams(params).toString(); | ||
|
|
||
| const response = await fetch( |
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를 사용해보는건 어떨까요?
현재 fetch를 그대로 사용해도 동작은 하겠으나, 반복되는 baseURL과 fetch로 받은 response에 대한 공통적인 후처리, request 보내기 전 공통적인 처리 등 또한 고려해볼 수 있을거예요.
다음 사항들을 고려해볼 수 있어요:
- 만약
get이 아닌 메써드(post,patch,delete등)일 경우는 어떻게 처리할 수 있을까요? query와body가 필요할 때는 어떻게 처리 할 수 있을까요?- 로그인 인가를 위한 토큰을 request 전에 자동으로 삽입할 수는 없을까요? (인증/인가를 자동으로 할 수 없을까요?)
- 처음 한 번에 Base URL을 지정할 수는 없을까요?
- Base URL을 사용하다가 다른 엔드포인트에 요청해야 할 때는 어떻게 할 수 있을까요?
이 모든 요구사항들을 반영한 아키텍쳐를 구상하기는 어렵습니다. 따라서 이 모든 요구사항이 잘 만족하는 http client를 사용해보시는건 어떨까요 ?
- Base URL을 사용하다가 다른 엔드포인트에 요청해야 할 때는 어떻게 할 수 있을까요?
instance를 만들어서 export를 하고 사용해보는 것 정도로 시도해보면 좋을 것 같아요. axios-instance 파일을 만들어서 instance를 생성하고 export한 후 사용해보는건 어떨까요?
다음과 같이 만들어볼 수 있어요:
const baseURL = process.env.NEXT_PUBLIC_LINKBRARY_BaseURL;
const instance = axios.create({
baseURL: baseURL,
headers: {
'Content-Type': 'application/json',
},
});
export default instance사용 방법 🚀
사용 방법은 정말 간단해요. 다음과 같이 사용할 수 있습니다:
instance.get(`/user/${userId}`)딱 보니. 마이그레이션도 정말 쉽게 할 수 있겠죠? 😊
|
|
||
| const PaginationBar = ({ totalPageNum, activePageNum, onPageChange }) => { | ||
| const maxVisiblePages = 5; |
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 PaginationBar = ({ totalPageNum, activePageNum, onPageChange }) => { | |
| const maxVisiblePages = 5; | |
| const MAX_VISIBLE_PAGES = 5; | |
| const PaginationBar = ({ totalPageNum, activePageNum, onPageChange }) => { |
좀 더 상세히 말하면 컴포넌트 내부 자원(props, 혹은 상태)을 사용하지 않는다면 외부에 선언해볼 수 있습니다. 😉
이렇게하면 리렌더링 시 불필요한 재선언을 방지할 수 있으며 최종적으로 컴포넌트 내부에는 컴포넌트 자원을 사용하는 코드가 남게 됩니다 👏
| const sortDropdown = () => { | ||
| setIsSortVisible(!isSortVisible); //true면 false로, false면 true로 | ||
| }; |
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.
updater function을 사용하면 최신 상태를 참조할 수 있습니다 !
| const sortDropdown = () => { | |
| setIsSortVisible(!isSortVisible); //true면 false로, false면 true로 | |
| }; | |
| const sortDropdown = () => { | |
| setIsSortVisible(prev => !prev); //true면 false로, false면 true로 | |
| }; |
위와 같이 사용해볼 수 있어요 😉
| return ( | ||
| <div> | ||
| <div className="allContainer"> | ||
| <h1 className="allTitle">전체 상품</h1> |
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.
<h1> 태그는 문서에 하나만 넣는게 어떨까요?
현재 여러 컴포넌트에서 h1 태그가 여럿 보이는군요 ! 🤔
이하 MDN 발췌
페이지 당 하나의 <h1>만 사용하세요. 여러 개를 써도 오류는 나지 않겠지만, 단일 <h1>이 모범 사례로 꼽힙니다. 논리적으로 생각했을 때도, <h1>은 가장 중요한 제목이므로 전체 페이지의 목적을 설명해야 할 것입니다. 두 개의 제목을 가진 책이나, 여러 개의 이름을 가진 영화는 볼 수 없죠! 또한 스크린 리더 사용자와 SEO에도 더 적합합니다.
| <nav> | ||
| <ul> | ||
| <li> | ||
| <NavLink to="/community" style={getLinkStyle}> |
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.
함수 사용하지 않고 다음과 같이 객체로 표현해도 무방합니다 😉
| <NavLink to="/community" style={getLinkStyle}> | |
| <NavLink to="/community" style={{ color: isActive ? 'var(--blue)' : undefined }}> |
|
수고하셨습니다 인화님 ! 미션 수행하시느라 정말 수고 많으셨습니다 ! |
요구사항
기본
중고마켓
중고마켓 반응형
심화
주요 변경사항
스크린샷
멘토에게
(윗줄: 타이틀, 상품 등록 버튼)
(아랫줄: 검색창, 소트 버튼) 으로 구현되어 있는데 이 부분을 어떻게 만들 수 있을까요...?