Skip to content

Conversation

@grimza99
Copy link
Collaborator

@grimza99 grimza99 commented Feb 24, 2025

요구사항

기본

  • 스프린트 미션 1 ~ 7에 대해 typescript를 적용해주세요

심화

  • any타입을 최소한으로 써주세요 -1개 사용 (이부분 주석으로남겨놨습니다)

주요 변경사항

  • 이번에도 file changed가 많습니다... 하핫
  • global.d.ts 파일에 전역 타입과 주석으로 된 질문이 있습니다

멘토에게

  • global.d.ts 파일에 주석으로 질문 남겨놓았습니다!!

nerte added 30 commits February 19, 2025 04:59
@grimza99 grimza99 self-assigned this Feb 24, 2025
@grimza99 grimza99 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Feb 24, 2025
@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 25, 2025

스프리트 미션 하시느라 수고 많으셨어요.
학습에 도움 되실 수 있게 꼼꼼히 리뷰 하도록 해보겠습니다. 😊

@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 25, 2025

global.d.ts 파일에 전역 타입과 주석으로 된 질문이 있습니다

넵넵 ! 확인해보겠습니다 !

tags: Tag[];
images: [string];
description: string;
[key: string]: any;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(질문에 대한 답변)혹시 [key: string]: any;를 지우는건 어떨까요?

Suggested change
[key: string]: any;

string| boolean| [string]|number로 하셔도 마찬가지입니다. 어떤 리소스가 어떤 타입인지 알 수가 없어지므로 타입스크립트를 쓰는 의미가 퇴색될 것 같아요.
해당 라인([key: string]: any;)을 지우시는게 좋을 것 같아요.

price: 0,
tags: [],
};
const REQUIRED_INPUT = ["name", "content", "tags", "price"];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(질문에 답변을 이어서) as const를 통하여 string이 아닌 유니온 타입으로 강제해볼게요.

Suggested change
const REQUIRED_INPUT = ["name", "content", "tags", "price"];
const REQUIRED_INPUT = ["name", "description", "tags", "price"] as const;

이렇게 하면 해당 타입은 readonly ["name", "description", "tags", "price"]이 됩니다.

그리고 content라고 작성주셨는데 코드 전문을 읽어보니 description이 맞는 것 같아서 임의로 수정하였습니다 😊

이렇게 진행하시면 에러가 나지 않을 거예요 ! 😊

Copy link
Collaborator

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에 에러 부분 주석 남겨놨습니다!)

Comment on lines +4 to +13
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다음과 같이 제네릭을 통해서 타입을 정의할 수 있어요 !

Suggested change
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;

Comment on lines 6 to 8
const res = await axios.get(
`${BASE_URL}/products/${productId}/comments?limit=${limit}`
);
Copy link
Collaborator

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

axios instance

인가에 필요한 accessTokenlocalStorage가 있다면 axios의 인터셉터를 활용할 수 있습니다 !

인터셉터는 혼자 해결해보시는 것을 권장드립니다. 혹시 모르시겠으면 다음 위클리 미션에 질문해주세요. 😊

사용 방법 🚀

사용 방법은 정말 간단해요. 다음과 같이 사용할 수 있습니다:

instance.get(`/user/${userId}`)

딱 보니. 마이그레이션도 정말 쉽게 할 수 있겠죠? 😊

axios API

Comment on lines +20 to +22
const order = selectedOrder === "최신순" ? "recent" : "favorite";
const pageSize = device === "mobile" ? 4 : device === "tablet" ? 6 : 10;
const query = `?orderBy=${order}&page=${page}&pageSize=${pageSize}`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿼리는 URLSearchParams로 손쉽게 사용할 수 있어요 !

Suggested change
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의 쿼리 문자열을 대상으로 작업할 수 있는 유틸리티 메서드를 정의합니다.

쿼리를 생성하실 때에 참고해서 사용해보세요 😊

Comment on lines +16 to +17
const [EditingId, setEditingId] = useState<number>(0);
const [DeleteId, setDeleteId] = useState<number>(0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(선택/의견) 초기 값이 넘버일 경우 자연스레 number 타입으로 추론됩니다 😊

Suggested change
const [EditingId, setEditingId] = useState<number>(0);
const [DeleteId, setDeleteId] = useState<number>(0);
const [EditingId, setEditingId] = useState(0);
const [DeleteId, setDeleteId] = useState(0);

Comment on lines +11 to +14
interface Props {
data: Comment;
}
export default function CommentCard({ data }: Props) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿굿 ~ props의 타입을 잘 설정하셨군요 👍

Comment on lines +9 to +37
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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(제안/주관적 의견) 관련된 타입은 사용될 코드와 가까이 두는게 어떨까요?

Suggested change
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이라는 타입은 ItemsListprops에만 사용되는 것 같아요. 관련된 컴포넌트 바로 위에 선언하여 가독성을 좋게하고, 해당 코드에는 두 props 타입이 존재하므로 이름을 명확히하는게 어떨까 제안드립니다 !

Comment on lines +18 to +20
type PageCountProps = Omit<Props, "onClick"> & {
onClick: (btn: number) => void;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유틸리티 타입을 정말 잘 활용하시네욤 ㄷㄷㄷ 👍

Comment on lines +6 to +19
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>
</>
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(제안) React에서 이미 선언된 기본 HTML 타입을 사용해보실 수도 있어요. 😉

Suggested change
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이 전달될거예요 😊

@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 25, 2025

크으... 정말 빠릅니다 선향님.
과제 달성률 1등이십니다 🏃‍♀️🏃‍♀️🏃‍♀️
질문에 대한 답변 남겼으니 확인해보세요 😉😉

타입 스크립트도 멋지게 해내셨군요. 특히 유틸리티 타입을 사용하시는 것 보면 배우신 것들을 정말 요목조목 잘 사용하시는 것 같아요.
훌륭합니다. ㅎㅎㅎ 미션 수행하시느라 고생 많으셨습니다 선향님.

추가로 declare 파일을 활용하여 앱의 전반에서 사용되는 리소스들을 글로벌 타입으로 정의하신 점 흥미롭네요 😊😊

@kiJu2 kiJu2 merged commit 51dd3c4 into codeit-bootcamp-frontend:React-유선향 Feb 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants