From 008eabd9416b826020a95390c082402bf5f4280b Mon Sep 17 00:00:00 2001 From: lurgi Date: Mon, 10 Jun 2024 14:02:32 +0900 Subject: [PATCH] =?UTF-8?q?API-Layer-&-Fetch-Functions=20=EB=B2=88?= =?UTF-8?q?=EC=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- June/article/API-Layer-&-Fetch-Functions.md | 199 ++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 June/article/API-Layer-&-Fetch-Functions.md diff --git a/June/article/API-Layer-&-Fetch-Functions.md b/June/article/API-Layer-&-Fetch-Functions.md new file mode 100644 index 0000000..4a25e17 --- /dev/null +++ b/June/article/API-Layer-&-Fetch-Functions.md @@ -0,0 +1,199 @@ +## ๐Ÿ”— [API Layer & Fetch Functions](https://profy.dev/article/react-architecture-api-layer-and-fetch-functions) + +### ๐Ÿ—“๏ธ ๋ฒˆ์—ญ ๋‚ ์งœ: 2024.06.10 + +### ๐Ÿงš ๋ฒˆ์—ญํ•œ ํฌ๋ฃจ: ๋Ÿฌ๊ธฐ(๋ฐ•์ •์šฐ) + +--- + +## ๋ฒˆ์—ญ ์ œ๋ชฉ + +# API๊ณ„์ธต & Fetch ํ•จ์ˆ˜ + +๋ฆฌ์•กํŠธ์˜ ์˜๊ฒฌ์ด ์—†๋Š” ํŠน์„ฑ์€ ์–‘๋‚ ์˜ ๊ฒ€๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +- ํ•œํŽธ์œผ๋กœ๋Š” ์„ ํƒ์˜ ์ž์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +- ๋‹ค๋ฅธ ํ•œํŽธ์œผ๋กœ ๋งŽ์€ ํ”„๋กœ์ ํŠธ๋“ค์ด ๋งž์ถคํ˜•์ด๊ณ  ์ข…์ข… ์ง€์ €๋ถ„ํ•œ ๊ตฌ์กฐ๋กœ ๋๋‚˜๊ณค ํ•ฉ๋‹ˆ๋‹ค. + +์ด ๊ธ€์€ ์†Œํ”„ํŠธ์›จ์–ด ์•„ํ‚คํ…์ฒ˜์™€ ๋ฆฌ์•กํŠธ ์•ฑ์— ๊ด€ํ•œ ์—ฐ์žฌ์˜ ๋‘ ๋ฒˆ์งธ ๋ถ€๋ถ„์œผ๋กœ, ์šฐ๋ฆฌ๋Š” ๋‚˜์œ ๊ด€ํ–‰์ด ๋งŽ์€ ์ฝ”๋“œ ๋ฒ ์ด์Šค๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ๋ฆฌํŒฉํ† ๋งํ•ด ๋‚˜๊ฐˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +[์ด์ „์—๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์˜ ๋ชจ๋“  ์š”์ฒญ ์‚ฌ์ด์—์„œ API ๊ธฐ๋ณธ URL๊ณผ ๊ฐ™์€ ๊ณตํ†ต ๊ตฌ์„ฑ์„ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•ด API ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ถ”์ถœํ–ˆ์Šต๋‹ˆ๋‹ค.](https://profy.dev/article/react-architecture-api-client) + +์ด๋ฒˆ ๊ธ€์—์„œ๋Š” API ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ UI ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐ ์ดˆ์ ์„ ๋งž์ถ”๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. + +![alt text](https://ik.imagekit.io/87wct6jq4ql/tr:w-1280/https://media.graphassets.com/RUDy4wgkRquKJoBUs6Pn) + +## ๋‚˜์œ ์ฝ”๋“œ์˜ ์˜ˆ์‹œ: API์™€ UI ์ฝ”๋“œ๊ฐ€ ์„ž์—ฌ ์žˆ์Œ + +๋‚˜์œ ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ์ด์ „ ๊ธ€์˜ ์ฒซ ๋ฒˆ์งธ ๋ฆฌํŒฉํ† ๋ง ๋‹จ๊ณ„์˜ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: + +๋‘ API ์—”๋“œํฌ์ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. + +```tsx +import { useEffect, useState } from "react"; +import { useParams } from "react-router"; +import { apiClient } from "@/api/client"; +import { LoadingSpinner } from "@/components/loading"; +import { ShoutList } from "@/components/shout-list"; +import { UserResponse, UserShoutsResponse } from "@/types"; +import { UserInfo } from "./user-info"; + +export function UserProfile() { + const { handle } = useParams<{ handle: string }>(); + + const [user, setUser] = useState(); + const [userShouts, setUserShouts] = useState(); + const [hasError, setHasError] = useState(false); + + useEffect(() => { + apiClient + .get(`/user/${handle}`) + .then((response) => setUser(response.data)) + .catch(() => setHasError(true)); + + apiClient + .get(`/user/${handle}/shouts`) + .then((response) => setUserShouts(response.data)) + .catch(() => setHasError(true)); + }, [handle]); + + if (hasError) { + return
An error occurred
; + } + + if (!user || !userShouts) { + return ; + } + + return ( +
+ + +
+ ); +} +``` + +## ์ด๊ฒŒ ์™œ ๋‚˜์œ ์ฝ”๋“œ์ธ๊ฐ€์š”? + +๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. API ์š”์ฒญ๊ณผ UI ์ฝ”๋“œ๊ฐ€ ์„์—ฌ์žˆ์Šต๋‹ˆ๋‹ค. API์ฝ”๋“œ์™€ UI์ฝ”๋“œ๊ฐ€ ์—ฌ๊ธฐ์ €๊ธฐ ์กฐ๊ธˆ์”ฉ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. + +![alt text](https://ik.imagekit.io/87wct6jq4ql/tr:w-1280/https://media.graphassets.com/Il7QrSIMRFuFq782XMOg) + +์‚ฌ์‹ค์ƒ, UI๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐ€์ ธ์˜ค๋Š”์ง€๋Š” ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +- GET, POST, ๋˜๋Š” PATCH ์š”์ฒญ์ด ๋ณด๋‚ด์ง€๋Š”์ง€ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. +- ์—”๋“œํฌ์ธํŠธ์˜ ์ •ํ™•ํ•œ ๊ฒฝ๋กœ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. +- ์š”์ฒญ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ API์— ์–ด๋–ป๊ฒŒ ์ „๋‹ฌ๋˜๋Š”์ง€ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. +- ์‹ฌ์ง€์–ด REST API์— ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ์›น์†Œ์ผ“์— ์—ฐ๊ฒฐ๋˜๋Š”์ง€์กฐ์ฐจ ์ •๋ง๋กœ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์ด ๋ชจ๋“  ๊ฒƒ์€ UI์˜ ๊ด€์ ์—์„œ ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +## ํ•ด๊ฒฐ์ฑ…: API ์—ฐ๊ฒฐ ํ•จ์ˆ˜๋ฅผ ์ถ”์ถœํ•œ๋‹ค. + +API์— ์—ฐ๊ฒฐํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ณ„๋„์˜ ์žฅ์†Œ๋กœ ์ถ”์ถœํ•˜๋ฉด API ๊ด€๋ จ ์ฝ”๋“œ์™€ UI ์ฝ”๋“œ์˜ ๊ฒฐํ•ฉ์„ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ํ•จ์ˆ˜๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์ˆจ๊น๋‹ˆ๋‹ค: + +- ์š”์ฒญ ๋ฐฉ์‹ +- ์—”๋“œํฌ์ธํŠธ ๊ฒฝ๋กœ +- ๋ฐ์ดํ„ฐ ์œ ํ˜• + +๋ถ„๋ช…ํ•œ ๋ถ„๋ฆฌ๋ฅผ ์œ„ํ•ด ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋“ค์„ ์ „์—ญ API ํด๋”์— ์œ„์น˜์‹œํ‚ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +```tsx +// src/api/user.ts + +import { User, UserShoutsResponse } from "@/types"; +import { apiClient } from "./client"; + +async function getUser(handle: string) { + const response = await apiClient.get<{ data: User }>(`/user/${handle}`); + return response.data; +} + +async function getUserShouts(handle: string) { + const response = await apiClient.get(`/user/${handle}/shouts`); + return response.data; +} + +export default { getUser, getUserShouts }; +``` + +์ด์ œ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ fetch ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +```tsx +import UserApi from "@/api/user"; + +... + +export function UserProfile() { + const { handle } = useParams<{ handle: string }>(); + + const [user, setUser] = useState(); + const [userShouts, setUserShouts] = useState(); + const [hasError, setHasError] = useState(false); + + useEffect(() => { + if (!handle) { + return; + } + + UserApi.getUser(handle) + .then((response) => setUser(response.data)) + .catch(() => setHasError(true)); + + UserApi.getUserShouts(handle) + .then((response) => setUserShouts(response)) + .catch(() => setHasError(true)); + }, [handle]); + + ... +``` + +## UI์ฝ”๋“œ์™€ API์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š”๊ฒƒ์ด ์™œ ๋” ์ข‹์€ ์ฝ”๋“œ์ฃ ? + +๋งž์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ์˜ ๋ณ€ํ™”๊ฐ€ ํฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์•ฝ๊ฐ„์˜ ์ฝ”๋“œ๋งŒ ์˜ฎ๊ฒผ์„ ๋ฟ์ž…๋‹ˆ๋‹ค. + +๊ฒฐ๊ณผ๋ฌผ์ด ์ฒ˜์Œ์—๋งŒ ์กฐ๊ธˆ ๊น”๋”ํ•˜๊ฒŒ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```tsx +// before +apiClient.get(`/user/${handle}`); + +// after +UserApi.getUser(handle); +``` + +๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์‹ค์ƒ, ์šฐ๋ฆฌ๋Š” UI์ฝ”๋“œ์™€ API ๊ด€๋ จ ํ•จ์ˆ˜๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. + +๋ฐ˜๋ณตํ•ด์„œ ๋งํ•˜๋Š” ๊ฒƒ์ด ์ง€๊ฒน์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ์ด์ œ ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋งŽ์€ API ๊ด€๋ จ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์•Œ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. + +- ์š”์ฒญ ๋ฐฉ์‹ GET +- ๋ฐ์ดํ„ฐ ์œ ํ˜• UserResponse์˜ ์ •์˜ +- ์—”๋“œํฌ์ธํŠธ ๊ฒฝ๋กœ /user/some-handle +- ๋˜๋Š” ํ•ธ๋“ค์ด URL ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ API์— ์ „๋‹ฌ๋œ๋‹ค๋Š” ์‚ฌ์‹ค + +๋Œ€์‹ , ํƒ€์ž…์ด ์ง€์ •๋œ ๊ฒฐ๊ณผ๋ฅผ ํ”„๋กœ๋ฏธ์Šค๋กœ ๊ฐ์‹ผ ๊ฐ„๋‹จํ•œ ํ•จ์ˆ˜ `UserApi.getUser(handle)`๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. + +๋˜ํ•œ, ์ด๋Ÿฌํ•œ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜๋“ค์„ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์žฌ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํด๋ผ์ด์–ธํŠธ ์ธก ๋˜๋Š” ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ ๋ Œ๋”๋ง ์ ‘๊ทผ ๋ฐฉ์‹์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๋‹ค์Œ ๋ฆฌํŒฉํ„ฐ๋ง ๋‹จ๊ณ„ + +๋„ค, ์šฐ๋ฆฌ๋Š” API๋กœ ๋ถ€ํ„ฐ UI์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š”๋ฐ ์ฒซ ๋ฒˆ์งธ ํฐ ์ง„์ „์„ ์ด๋ฃจ์—ˆ์Šต๋‹ˆ๋‹ค. API๊ณ„์ธต์„ ๋„์ž…ํ•˜๊ณ , UI์ปดํฌ๋„ŒํŠธ์— ๊ตฌํ˜„๋œ ๋งŽ์€ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์ œ๊ฑฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฌ๋‚˜ ์—ฌ์ „ํžˆ API ๊ณ„์ธต๊ณผ ์šฐ๋ฆฌ ์ปดํฌ๋„ŒํŠธ ์‚ฌ์ด์— ์ƒ๋‹นํ•œ ๊ฒฐํ•ฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์šฐ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค: + +![alt text](https://ik.imagekit.io/87wct6jq4ql/tr:w-1280/https://media.graphassets.com/lJjVG80BRVi817s4vOwF) + +์ด๊ฒŒ ์ธ์ƒ์ ์ธ ์˜ˆ์‹œ๊ฐ€ ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ์ฝ”๋“œ์˜ ๋‹ค๋ฅธ ์˜ˆ์‹œ๋ฅผ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +![alt text](https://ik.imagekit.io/87wct6jq4ql/tr:w-1280/https://media.graphassets.com/6FUzRWpTSbmufwlBQQy5) + +์—ฌ๊ธฐ์—์„œ ์šฐ๋ฆฌ๋Š” ํ”ผ๋“œ์˜ ์‘๋‹ต์— ์‚ฌ์šฉ์ž์™€ ์ด๋ฏธ์ง€๋ฅผ ํฌํ•จํ•˜๋Š” ํ•„๋“œ๊ฐ€ ์žˆ์Œ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ์˜์ง€๋Š” ์•Š์ง€๋งŒ, ๋•Œ๋•Œ๋กœ ์šฐ๋ฆฌ๋Š” ์ด๋Ÿฌํ•œ API๋ฅผ ๋‹ค๋ฃจ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ๋ฐ ์™œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ด๊ฒƒ์„ ์•Œ์•„์•ผ ํ• ๊นŒ์š”? + +์–ด์จŒ๋“ , ์ด ๋ฌธ์ œ๋Š” ๋‹ค์Œ ์•„ํ‹ฐํด์—์„œ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค.