-
Notifications
You must be signed in to change notification settings - Fork 20
[유선향]-sprint8 #71
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
[유선향]-sprint8 #71
The head ref may contain hidden characters: "React-\uC720\uC120\uD5A5-sprint8"
Conversation
|
스프리트 미션 하시느라 수고 많으셨어요. |
global.d.ts 파일에 전역 타입과 주석으로 된 질문이 있습니다넵넵 ! 확인해보겠습니다 ! |
| tags: Tag[]; | ||
| images: [string]; | ||
| description: string; | ||
| [key: string]: any; |
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.
(질문에 대한 답변)혹시 [key: string]: any;를 지우는건 어떨까요?
| [key: string]: any; |
string| boolean| [string]|number로 하셔도 마찬가지입니다. 어떤 리소스가 어떤 타입인지 알 수가 없어지므로 타입스크립트를 쓰는 의미가 퇴색될 것 같아요.
해당 라인([key: string]: any;)을 지우시는게 좋을 것 같아요.
| price: 0, | ||
| tags: [], | ||
| }; | ||
| const REQUIRED_INPUT = ["name", "content", "tags", "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.
(질문에 답변을 이어서) as const를 통하여 string이 아닌 유니온 타입으로 강제해볼게요.
| const REQUIRED_INPUT = ["name", "content", "tags", "price"]; | |
| const REQUIRED_INPUT = ["name", "description", "tags", "price"] as const; |
이렇게 하면 해당 타입은 readonly ["name", "description", "tags", "price"]이 됩니다.
그리고
content라고 작성주셨는데 코드 전문을 읽어보니description이 맞는 것 같아서 임의로 수정하였습니다 😊
이렇게 진행하시면 에러가 나지 않을 거예요 ! 😊
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.
(참고) git diff를 통한 수정사항:
@@ -14,7 +14,7 @@ const INITIAL_DATA: ProductForm = {
price: 0,
tags: [],
};
-const REQUIRED_INPUT = ["name", "content", "tags", "price"];
+const REQUIRED_INPUT = ["name", "description", "tags", "price"] as const;
//
function AddItem() {
const [tag, setTag] = useState<string>("");
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index bfd0b61..c120cc4 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -16,7 +16,7 @@ declare global {
tags: Tag[];
images: [string];
description: string;
- [key: string]: any;
+ // [key: string]: any;
//원래 any 부분에 string| boolean| [string]|number 를 적었었는데,
//유니온 타입에 이터러블이 아닌 값이 섞여있어서 includes랑 스프레드가 안되더라고요... ㅠㅠ
//인덱스 시그니쳐를 지우면 addItem 에서 에러가 나서 방법을 찾아보다가 도저히 모르겠어서 any 로 지정했습니다! (addItem에 에러 부분 주석 남겨놨습니다!)| export async function getProductComments({ productId }: Params, limit = 3) { | ||
| try { | ||
| const res = await axios.get( | ||
| `${BASE_URL}/products/${productId}/comments?limit=${limit}` | ||
| ); | ||
| if (!res) { | ||
| throw new Error("리뷰 불러오기 실패"); | ||
| } | ||
| return res.data; | ||
| const data: Comment[] = res.data.list; | ||
| return data; |
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 async function getProductComments({ productId }: Params, limit = 3) { | |
| try { | |
| const res = await axios.get( | |
| `${BASE_URL}/products/${productId}/comments?limit=${limit}` | |
| ); | |
| if (!res) { | |
| throw new Error("리뷰 불러오기 실패"); | |
| } | |
| return res.data; | |
| const data: Comment[] = res.data.list; | |
| return data; | |
| export async function getProductComments({ productId }: Params, limit = 3) { | |
| try { | |
| const res = await axios.get<Comment[]>( // <-- 여기요 ! | |
| `${BASE_URL}/products/${productId}/comments?limit=${limit}` | |
| ); | |
| if (!res) { | |
| throw new Error("리뷰 불러오기 실패"); | |
| } | |
| const data = res.data.list; | |
| return data; |
| const res = await axios.get( | ||
| `${BASE_URL}/products/${productId}/comments?limit=${limit}` | ||
| ); |
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를 사용중이시라면 instnace를 생성하셔서 사용해보실 것을 권장드립니다 !
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인가에 필요한 accessToken을 localStorage가 있다면 axios의 인터셉터를 활용할 수 있습니다 !
인터셉터는 혼자 해결해보시는 것을 권장드립니다. 혹시 모르시겠으면 다음 위클리 미션에 질문해주세요. 😊
사용 방법 🚀
사용 방법은 정말 간단해요. 다음과 같이 사용할 수 있습니다:
instance.get(`/user/${userId}`)딱 보니. 마이그레이션도 정말 쉽게 할 수 있겠죠? 😊
| const order = selectedOrder === "최신순" ? "recent" : "favorite"; | ||
| const pageSize = device === "mobile" ? 4 : device === "tablet" ? 6 : 10; | ||
| const query = `?orderBy=${order}&page=${page}&pageSize=${pageSize}`; |
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.
쿼리는 URLSearchParams로 손쉽게 사용할 수 있어요 !
| const order = selectedOrder === "최신순" ? "recent" : "favorite"; | |
| const pageSize = device === "mobile" ? 4 : device === "tablet" ? 6 : 10; | |
| const query = `?orderBy=${order}&page=${page}&pageSize=${pageSize}`; | |
| const order = selectedOrder === "최신순" ? "recent" : "favorite"; | |
| const pageSize = device === "mobile" ? 4 : device === "tablet" ? 6 : 10; | |
| const query = new URLSearchParams({order, page, pageSize}).toString(); |
URLSearchParams와 함께 객체로 손쉽게 핸들링할 수 있습니다 !
객체로 구성할 수 있어 가독성이 좋고, URL 인코딩을 자동으로 처리하여 특수 문자나 공백이 포함된 값에서도 안전하게 동작합니다 !
URLSearchParams:
URLSearchParams인터페이스는 URL의 쿼리 문자열을 대상으로 작업할 수 있는 유틸리티 메서드를 정의합니다.
쿼리를 생성하실 때에 참고해서 사용해보세요 😊
| const [EditingId, setEditingId] = useState<number>(0); | ||
| const [DeleteId, setDeleteId] = useState<number>(0); |
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.
(선택/의견) 초기 값이 넘버일 경우 자연스레 number 타입으로 추론됩니다 😊
| const [EditingId, setEditingId] = useState<number>(0); | |
| const [DeleteId, setDeleteId] = useState<number>(0); | |
| const [EditingId, setEditingId] = useState(0); | |
| const [DeleteId, setDeleteId] = useState(0); |
| interface Props { | ||
| data: Comment; | ||
| } | ||
| export default function CommentCard({ data }: Props) { |
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.
굿굿 ~ props의 타입을 잘 설정하셨군요 👍
| interface Prop { | ||
| value: string; | ||
| items: Item[]; | ||
| } | ||
| interface ListItemProps { | ||
| value: string; | ||
| item: Item; | ||
| } | ||
| function ListItem({ value, item }: ListItemProps) { | ||
| const navigate = useNavigate(); | ||
| const device = useWindowSize(); | ||
| return ( | ||
| <S.Item onClick={() => navigate(`./${item.id}`)}> | ||
| <S.ProductImg | ||
| value={value} | ||
| $device={device} | ||
| src={item.images[0]} | ||
| alt="이미지" | ||
| /> | ||
| <S.FlexContent> | ||
| <S.Title>{item.name}</S.Title> | ||
| <S.Price>{item.price} 원</S.Price> | ||
| <BtnHeart $items value={item.favoriteCount} /> | ||
| </S.FlexContent> | ||
| </S.Item> | ||
| ); | ||
| } | ||
| // | ||
| export default function ItemsList({ value, items, ...props }: Prop) { |
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.
(제안/주관적 의견) 관련된 타입은 사용될 코드와 가까이 두는게 어떨까요?
| interface Prop { | |
| value: string; | |
| items: Item[]; | |
| } | |
| interface ListItemProps { | |
| value: string; | |
| item: Item; | |
| } | |
| function ListItem({ value, item }: ListItemProps) { | |
| const navigate = useNavigate(); | |
| const device = useWindowSize(); | |
| return ( | |
| <S.Item onClick={() => navigate(`./${item.id}`)}> | |
| <S.ProductImg | |
| value={value} | |
| $device={device} | |
| src={item.images[0]} | |
| alt="이미지" | |
| /> | |
| <S.FlexContent> | |
| <S.Title>{item.name}</S.Title> | |
| <S.Price>{item.price} 원</S.Price> | |
| <BtnHeart $items value={item.favoriteCount} /> | |
| </S.FlexContent> | |
| </S.Item> | |
| ); | |
| } | |
| // | |
| export default function ItemsList({ value, items, ...props }: Prop) { | |
| interface ListItemProps { | |
| value: string; | |
| item: Item; | |
| } | |
| function ListItem({ value, item }: ListItemProps) { | |
| const navigate = useNavigate(); | |
| const device = useWindowSize(); | |
| return ( | |
| <S.Item onClick={() => navigate(`./${item.id}`)}> | |
| <S.ProductImg | |
| value={value} | |
| $device={device} | |
| src={item.images[0]} | |
| alt="이미지" | |
| /> | |
| <S.FlexContent> | |
| <S.Title>{item.name}</S.Title> | |
| <S.Price>{item.price} 원</S.Price> | |
| <BtnHeart $items value={item.favoriteCount} /> | |
| </S.FlexContent> | |
| </S.Item> | |
| ); | |
| } | |
| // | |
| interface ItemsListProps { | |
| value: string; | |
| items: Item[]; | |
| } | |
| export default function ItemsList({ value, items, ...props }: ItemsListProps) { |
기존 Props이라는 타입은 ItemsList의 props에만 사용되는 것 같아요. 관련된 컴포넌트 바로 위에 선언하여 가독성을 좋게하고, 해당 코드에는 두 props 타입이 존재하므로 이름을 명확히하는게 어떨까 제안드립니다 !
| type PageCountProps = Omit<Props, "onClick"> & { | ||
| onClick: (btn: number) => void; | ||
| }; |
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.
유틸리티 타입을 정말 잘 활용하시네욤 ㄷㄷㄷ 👍
| interface Props extends S.ButtonProps { | ||
| onClick: onClick; | ||
| children: ReactNode; | ||
| disabled?: boolean; | ||
| } | ||
| export default function Button({ onClick, children, ...props }: Props) { | ||
| return ( | ||
| <> | ||
| <S.Button onClick={onClick} {...props}> | ||
| {children} | ||
| </S.Button> | ||
| </> | ||
| ); | ||
| } |
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에서 이미 선언된 기본 HTML 타입을 사용해보실 수도 있어요. 😉
| interface Props extends S.ButtonProps { | |
| onClick: onClick; | |
| children: ReactNode; | |
| disabled?: boolean; | |
| } | |
| export default function Button({ onClick, children, ...props }: Props) { | |
| return ( | |
| <> | |
| <S.Button onClick={onClick} {...props}> | |
| {children} | |
| </S.Button> | |
| </> | |
| ); | |
| } | |
| import * as S from "./Button.style"; | |
| import { ButtonHTMLAttributes } from "react"; | |
| interface Props extends ButtonHTMLAttributes<HTMLButtonElement> { | |
| children: React.ReactNode; | |
| } | |
| export default function Button({ children, ...props }: Props) { | |
| return ( | |
| <> | |
| <S.Button {...props}>{children}</S.Button> | |
| </> | |
| ); | |
| } |
만약 html 버튼에 있는 type, onBlur, clssName 등이 필요하게 button.tsx를 새로운 props가 필요할 때 마다 지속적으로 수정해야 될거예요. 만약 협업중이었다면 공통 컴포넌트이니 만큼 다른 개발자들도 사용하고 있으므로 충돌이 날 수도 있지요 !
기존
onClick을 지웠어요. 전달 역할만 수행하며 별도의 추가 로직이 없기에...props내에onClcik이 전달될거예요 😊
|
크으... 정말 빠릅니다 선향님. 타입 스크립트도 멋지게 해내셨군요. 특히 유틸리티 타입을 사용하시는 것 보면 배우신 것들을 정말 요목조목 잘 사용하시는 것 같아요.
|
요구사항
기본
심화
주요 변경사항
멘토에게