Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 108 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.5",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@types/styled-components": "^5.1.34",
"axios": "^1.7.9",
"lodash.debounce": "^4.0.8",
"react": "^18.2.0",
Expand Down Expand Up @@ -39,5 +44,10 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.9",
"@types/react-js-pagination": "^3.0.7",
"typescript": "^4.9.5"
}
}
File renamed without changes.
11 changes: 0 additions & 11 deletions src/api/comment.js

This file was deleted.

24 changes: 24 additions & 0 deletions src/api/comment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import api from "./index";

export interface Comment {
id: number;
content: string;
createdAt: string;
updatedAt: string;
writer: {
id: number;
image: string | null;
nickname: string;
};
}

export async function getComments(
productId: string,
limit: number = 3
): Promise<Comment[]> {
const response = await api.get(
`/products/${productId}/comments?limit=${limit}`
);

return response.data.list;
}
12 changes: 12 additions & 0 deletions src/api/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import axios from "axios";

const BASE_URL = process.env.REACT_APP_BASE_URL;

const api = axios.create({
baseURL: BASE_URL,
headers: {
"Content-Type": "application/json",
},
});

export default api;
18 changes: 0 additions & 18 deletions src/api/products.js

This file was deleted.

40 changes: 40 additions & 0 deletions src/api/products.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { AddItem } from "../components/pages/AddItemPage/AddItemPage";
import api from "./index";

export interface Items {
id: string;
images: string;
name: string;
price: number;
favoriteCount: number;
}

interface Params {
page: number;
pageSize: number;
orderBy: string;
keyword: string;
}

export interface DetailItem extends Omit<AddItem, "images"> {
images: string | null;
favoriteCount: number;
createdAt: string;
updatedAt: string;
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가 "어떤 값"을 받는지에 따라 반환되는 타입을 명시할 수 있습니다.

const { page, pageSize, orderBy, keyword } = params;
const response = await api.get(`/products`, {
params: { page, pageSize, orderBy, keyword },
});

return response.data;
}
Comment on lines +27 to +34
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"


export async function getProductInfo(productId: string): Promise<DetailItem> {
const response = await api.get(`/products/${productId}`);

return response.data;
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as S from "./CommentInput.styles";
import { useState } from "react";

export default function CommentInput() {
const [comment, setComment] = useState("");
const [comment, setComment] = useState<string>("");

return (
<S.CommentContainer>
Expand All @@ -14,7 +14,9 @@ export default function CommentInput() {
largeHeight="140px"
isTextarea
value={comment}
onChange={(e) => setComment(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setComment(e.target.value)
}
/>
<S.RegisterBtn>
<button disabled={!comment.trim()} onClick={() => setComment("")}>
Expand Down
Loading
Loading