Skip to content

Conversation

@heewls
Copy link
Collaborator

@heewls heewls commented Feb 25, 2025

요구사항

기본

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

심화

  • any타입을 최소한으로 써주세요

주요 변경사항

스크린샷

멘토에게

  • api product.tsx 파일 안 getProducts 함수의 Promise 타입 지정이 어렵습니다. AllItems와 BestItems component 두 곳에서 사용하고 있습니다. Promise<AllItem | BestItem> 이런 식으로 type 지정을 시도했지만 오류를 고치지 못해 타입 지정을 하지 못했습니다. 피드백 부탁드립니다! (interface AllItem은 AllItems.tsx 파일에 BestItem은 BestItems.tsx 파일에 있습니다)

@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 26, 2025

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

@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 26, 2025

api product.tsx 파일 안 getProducts 함수의 Promise 타입 지정이 어렵습니다. AllItems와 BestItems component 두 곳에서 사용하고 있습니다. Promise<AllItem | BestItem> 이런 식으로 type 지정을 시도했지만 오류를 고치지 못해 타입 지정을 하지 못했습니다. 피드백 부탁드립니다! (interface AllItem은 AllItems.tsx 파일에 BestItem은 BestItems.tsx 파일에 있습니다)

일반적인 방법으로 희진님께서 작성주신 반환타입을 유니온 타입을(Promise<AllItem | BestItem>) 타입 가드를 통하여 해결할 수 있을 것 같아요.
다만, 희진님의 학습 레벨을 고려해서 조금 더 근본적인 방법을 제안드려볼게요 ! (희진님은 잘 이해하시고 따라올 수 있다고 생각이 들어서요 ! 😉)
(코드리뷰로 이어서 답변드리겠습니다)

ownerNickname: string;
}

export async function getProducts(params: Params) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

근본적인 문제는 "모호함" 이라고 사료됩니다.

같은 엔드포인트이지만 favorite, recent에 따라 기대하는 반환값이 다르기에 생겨난 문제로 보입니다 !
기능의 목적이 다르며, 기대하는 반환되는 값도 다릅니다.
이럴 때 'A' 값이 들어오면 'AA' 값이 반환됨을 컴파일 단계에서 확정을 해줘야 하는데.
현재는 'A' 값이 들어오면 'AA'이거나 'BB'일 수도 있습니다.

이럴 때는 타입 가드를 통하여 타입을 확정지을 수도 있겠으나,
오버로딩(Overloading)을 통하여 해결할 수도 있어요.

오버로딩(Overloading): 오버로딩은 메서드의 이름은 같지만, 타입은 다르게 정의하여 사용하는 방법이다.

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 getProducts(params: Params) {
export async function getProducts(params: RecentParams): Promise<AllItem>;
export async function getProducts(params: FavoriteParams): Promise<BestItem>;
export async function getProducts(params: RecentParams | FavoriteParams) {

이를 테면, getProducts가 "어떤 값"을 받는지에 따라 반환되는 타입을 명시할 수 있습니다.

Comment on lines +27 to +34
export async function getProducts(params: Params) {
const { page, pageSize, orderBy, keyword } = params;
const response = await api.get(`/products`, {
params: { page, pageSize, orderBy, keyword },
});

return response.data;
}
Copy link
Collaborator

@kiJu2 kiJu2 Feb 26, 2025

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 getProducts(params: Params) {
const { page, pageSize, orderBy, keyword } = params;
const response = await api.get(`/products`, {
params: { page, pageSize, orderBy, keyword },
});
return response.data;
}
export async function getProducts(params: RecentParams | FavoriteParams) {
const { page, pageSize, orderBy, keyword } = params;
if (orderBy === "favorite" && keyword === "") {
const response = await api.get<BestItem>(`/products`, {
params: { page, pageSize, orderBy, keyword },
});
return response.data;
}
const response = await api.get<AllItem>(`/products`, {
params: { page, pageSize, orderBy, keyword },
});
return response.data;
}

희진님께서 BestItems.tsx에 작성주신 다음 코드를 통해 favorite일 경우 파라메터 유추하였습니다:

    getProducts({
      page: 1,
      pageSize: 4,
      orderBy: "favorite",
      keyword: "",
    }).then((result: BestItem) => {
      if (!result) return;
      const sortedBestItems = [...result.list].slice(0, 4);
      setBestItems(sortedBestItems);
    });

Copy link
Collaborator

Choose a reason for hiding this comment

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

이 외 고려해야할 점

현재 제 코드는 AllItems.tsxBestItems.tsx에서 반환 타입을 import하고 있어요.
이는 데이터 통신 레이어에서 특정 UI 레이어(컴포넌트)의 타입을 받아오고 있으므로 좋지 못할 수 있습니다.

products.ts에서 Response 타입을 정의해보는 방법도 있을 것 같네요. 😊

Copy link
Collaborator

Choose a reason for hiding this comment

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

(참조) 변경 코드 전문:

diff --git a/src/api/products.tsx b/src/api/products.tsx
index 42c1f45..9c3e19a 100644
--- a/src/api/products.tsx
+++ b/src/api/products.tsx
@@ -1,3 +1,5 @@
+import { AllItem } from "../components/Items/AllItems/AllItems";
+import { BestItem } from "../components/Items/BestItems/BestItems";
 import { AddItem } from "../components/pages/AddItemPage/AddItemPage";
 import api from "./index";

@@ -9,13 +11,20 @@ export interface Items {
   favoriteCount: number;
 }

-interface Params {
+interface RecentParams {
   page: number;
   pageSize: number;
-  orderBy: string;
+  orderBy: "recent" | "favorite";
   keyword: string;
 }

+interface FavoriteParams {
+  page: number;
+  pageSize: number;
+  orderBy: "favorite";
+  keyword: "";
+}
+
 export interface DetailItem extends Omit<AddItem, "images"> {
   images: string | null;
   favoriteCount: number;
@@ -24,9 +33,20 @@ export interface DetailItem extends Omit<AddItem, "images"> {
   ownerNickname: string;
 }

-export async function getProducts(params: Params) {
+export async function getProducts(params: RecentParams): Promise<AllItem>;
+export async function getProducts(params: FavoriteParams): Promise<BestItem>;
+export async function getProducts(params: RecentParams | FavoriteParams) {
   const { page, pageSize, orderBy, keyword } = params;
-  const response = await api.get(`/products`, {
+
+  if (orderBy === "favorite" && keyword === "") {
+    const response = await api.get<BestItem>(`/products`, {
+      params: { page, pageSize, orderBy, keyword },
+    });
+
+    return response.data;
+  }
+
+  const response = await api.get<AllItem>(`/products`, {
     params: { page, pageSize, orderBy, keyword },
   });

Copy link
Collaborator

Choose a reason for hiding this comment

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

추가로, 오버로딩과 관련된 예시 코드를 작성해봤어요 ! 한 번 확인해보세요 😊:

interface Cat {
  status: "live" | "deaded"
  inWater: false
}

interface Fish {
  status: "live" | "deaded"
  inWater: true
}

type CatResponse = "is Cat"

type FishResponse = "is Fish"

function getAnimal(param: Cat): CatResponse
function getAnimal(param: Fish): FishResponse
function getAnimal(param: Cat | Fish) {
  if (param.inWater) {
    return "is Fish"
  }

  return "is Cat"
}

const who = getAnimal({
  status: "live",
  inWater: false
}) // who의 타입은 "is Cat"

Comment on lines +19 to +22
getComments(productId)
.then((result) => setComments(result))
.catch((error) => console.error(error));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

UI 계층에서 에러 발생 시 사용자 피드백도 함께 해주면 어떨까요?

        console.error(error);
        alert(error.message);

설치 필요 없지.. 대부분의 브라우저와 디바이스에서 사용 가능하지...
alert는 정말 훌륭한 사용자 피드백 브라우저 API 입니다 !

Comment on lines +16 to +26
const INITIAL_VALUE = {
name: "",
description: "",
price: 0,
tags: [],
images: null,
favoriteCount: 0,
createdAt: "",
updatedAt: "",
ownerNickname: "",
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

굿굿 ! 컴포넌트 바깥에 선언을 하셨군요 ! 👍👍

피드백 반영 넘 좋습니당 😊

@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 26, 2025

수고하셨습니다 희진님 !
항상 너무 잘해주시고 계시기에 희진님 맞춤으로 피드백을 제안해봤어요 !
질문 주신 내용과 관련 코드 외에 피드백 반영을 위주의 변경사항과 타입 지정이 대부분이군요 !

타입스크립트 잘 적용하셨습니다. 오버로딩도 한 번 고민해보시면 좋을 것 같아요.
또한 질문 내용을 보니 타입스크립트을 사용하는 이유를 명확히 아시는 것 같군요 😊👍

스프린트 미션 수행하시느라 정말 수고 많으셨습니다 희진님 !

@kiJu2 kiJu2 merged commit 2c4c912 into codeit-bootcamp-frontend:React-김희진 Feb 26, 2025
@heewls heewls self-assigned this Feb 26, 2025
@heewls heewls added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Feb 26, 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