๐ฆ 04. ์ ๋ค๋ฆญ(Generics): ์์๋ค ๋ง๋ฅ API ์๋ต ๋ํผ ๋ง๋ค๊ธฐ
๐ ๊ฐ์
ํ์ ์ ๋ณ์์ฒ๋ผ ๋๊ฒจ์ฃผ๋ ๋ง๋ฒ, ๊ทธ๋ฆฌ๊ณ extends๋ฅผ ํ์ฉํ ์ ๋ค๋ฆญ ์ ์ฝ ์กฐ๊ฑด
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 12๋ถ(์ ์ฒด) / ํต์ฌ ํํธ๋ง: 7๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[๋ฐ๋ณต๋๋ ํ์
๋ณต๋ถ์ ๋ช] โ [์ ๋ค๋ฆญ: ํ์
์ ๋งค๊ฐ๋ณ์ํ] โ [์๋ฌด๊ฑฐ๋ ๋ค์ด์ค๋ ๊ฑด ์ซ์ด: extends ์ ์ฝ]
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
<T>๊ฐ ํจ์ ํ๋ผ๋ฏธํฐ์ ์ ํํ ๋๊ฐ์ ๊ฐ๋ ์์ ์ค๋ช ํ ์ ์๋ค. - ๋ฐ๋ณต๋๋ API ์๋ต ๊ตฌ์กฐ๋ฅผ ํ๋์ ์ ๋ค๋ฆญ ์ธํฐํ์ด์ค๋ก ์ฐ์ํ๊ฒ ํต์ผํ ์ ์๋ค.
-
extendsํค์๋๋ฅผ ์ฌ์ฉํด ์ ๋ค๋ฆญ์ด ๊ฐ์ง ์ ์๋ ํ์ ์ ๋ฒ์๋ฅผ ์์ ํ๊ฒ ์ ํํ ์ ์๋ค.
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ ( ์ ์
): "(ํ๋ฅํ๋ฅ... ์ ๋ณต์ฌ ๋ถ์ฌ๋ฃ๊ธฐ ์์ด๋ฌ๋ค) ํ์... API ์๋ํฌ์ธํธ๊ฐ ๋์ด๋ ๋๋ง๋ค
ApiResponse_User,ApiResponse_Board,ApiResponse_Comment๋ค ๋ฐ๋ก ๋ง๋ค๊ณ ์๋๋ฐ,statusCode๋messageํ๋๋ ๋งจ๋ ๋๊ฐ์ด ๋ค์ด๊ฐ๋ค์. ์๊ฐ๋ฝ ์ํ ์ฃฝ๊ฒ ์ต๋๋ค." - ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "์์ฒ ๋, ํน์ ํจ์ ๋ง๋ค ๋ ์ค๋ณต๋๋ ๊ฐ์ด ์์ผ๋ฉด ๋งค๋ฒ ํจ์๋ฅผ 100๊ฐ ๋ง๋๋์? ๋งค๊ฐ๋ณ์(Parameter)๋ก ๋นผ๋ฒ๋ฆฌ์์์. ํ์ ๋ ๋๊ฐ์์. ๋ณํ๋ ๋ถ๋ถ๋ง ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ๋ฐ๋ ๋ง๋ฒ, ๊ทธ๊ฒ ๋ฐ๋ก ์ ๋ค๋ฆญ(Generics)์ ๋๋ค."
๐ค ์ ์์์ผ ํ๋๊ฐ: '๋ณต๋ถ'์ ์ฃ์ ์ด๋ค
ํ๋์ ์ธ ํ๋ก ํธ์๋ ํ๊ฒฝ์์ ์ธ๋ถ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋, ๋ฐฑ์๋ ๊ตฌ์ฑ์๋ค์ ๋์ฒด๋ก ๊ณตํต๋ ๋ดํฌ(Wrapper) ์์ ๋ฐ์ดํฐ๋ฅผ ๋ด์์ ์ค๋๋ค.
์ด๋ฅผํ ๋ฉด, ์์๋ค ๋ฐฑ์๋๋ ํญ์ ์ด๋ฐ ํฌ๋งท์ ๊ณ ์งํฉ๋๋ค.
{
"statusCode": 200,
"message": "์ฑ๊ณต!",
"data": { ...์ฌ๊ธฐ์ ์ง์ง ๋ฐ์ดํฐ... }
}๋ง์ฝ ์ ๋ค๋ฆญ์ ๋ชจ๋ฅธ๋ค๋ฉด, ์์ฒ ์ด์ ์ฝ๋๋ ๋์ฐํ๊ฒ ๋น๋ํด์ง ์๋ฐ์ ์์ต๋๋ค.
๐ฃ ์์ฒ ์ด์ ๊ณผ๊ฑฐ (๋ ธ๊ฐ๋ค ํ์ ์ ์ธ)
// ์ ์ ๋ฅผ ๊ฐ์ ธ์ฌ ๋
interface UserResponse {
statusCode: number;
message: string;
data: { id: number; name: string };
}
// ๊ฒ์๊ธ์ ๊ฐ์ ธ์ฌ ๋
interface BoardResponse {
statusCode: number; // ์ค๋ณต!
message: string; // ์ค๋ณต!
data: { id: number; title: string; content: string }[];
}์ด ์ฝ๋๋ ์ค๋ฌด์์ 100๊ฐ์ API๋ฅผ ๋ถ์ด๋ฉด 100๋ฒ ๋๊ฐ์ statusCode์ message๋ฅผ ์จ์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ค ๋ฐฑ์๋๊ฐ message๋ฅผ msg๋ก ๋ฐ๊พธ๋ฉด? ์ผ๊ทผ ํ์ ์
๋๋ค.
๐ฉ 1. ์ ๋ค๋ฆญ์ ๋ณธ์ง: ํ์ ๋ ๋ณ์๋ค
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํจ์์ ํจ๋ฌ๋ค์์ ํ์ ์ ์ธ๊ณ๋ก ํ์ณ ์ต๋๋ค.
function hello(name) ์ด๋ผ๋ ํจ์๊ฐ ์์ ๋, ๊ดํธ ์์ name ์๋ฆฌ์ "์์ฒ "์ ๋ฃ์์ง "์ํธ"๋ฅผ ๋ฃ์์ง๋ ๋์ค์ ํจ์๋ฅผ ํธ์ถํ ๋ ๊ฒฐ์ ๋ฉ๋๋ค.
์ ๋ค๋ฆญ๋ ์์ ํ ๋๊ฐ์ต๋๋ค. ๊บพ์ (< >) ์์ ์ธ ์ํ๋ฒณ(์ฃผ๋ก T, U, A ๋ฑ์ ์๋๋ค)์ ๋์ค์ ๊ฒฐ์ ๋ ๋ฐ์ดํฐ์ ๋ชจ์์ ๋ด์๋ "๋ณ์ ๋ฐ๊ตฌ๋"์ผ ๋ฟ์
๋๋ค.
๐ฆ ์ํธ์ ์ฐ์ํ ๋ง๋ฅ ๋ํผ (Generic Wrapper)
// ์ํธ: "์, ๋ฐ๋์ง ์๋ ๋ดํฌ(statusCode, message)๋ ๋๋๊ณ ,
// ์์ ๋ฐ๋ ์๋งน์ด(data) ์๋ฆฌ๋ง <T> ๋ผ๋ ๋ณ์๋ก ๋ซ์ด๋๋ ๊ฒ๋๋ค."
interface ApiResponse<T> {
statusCode: number;
message: string;
data: T; // ์ฌ๊ธฐ์ ๋์ค์ ๋๊ฒจ์ค ํ์
์ด ๊ทธ๋๋ก ๋ฐํ!
}์ด์ ์์ฒ ์ด๋ API ์๋ต์ ๋ค์๊ณผ ๊ฐ์ด ํ ๋ฐฉ์ ์ ๋ฆฌํ ์ ์์ต๋๋ค.
type User = { id: number; name: string };
type Board = { id: number; title: string; content: string };
// ์ฌ์ฉํ ๋, < > ์์ ์ํ๋ ์๋งน์ด๋ฅผ ๋์ ธ์ค๋ค!
const userRes: ApiResponse<User> = await fetchUser();
const boardsRes: ApiResponse<Board[]> = await fetchBoards(); // ๋ฐฐ์ด๋ ํต์งธ๋ก ๋ฃ์ ์ ์์!๐ก ์ ํํ ๋๋ฌธ์ T์ธ๊ฐ์?
Type์ ์ฝ์์ ๋๋ค. ์ผ๋ฐ ๋ณ์๋ฅผa,b,c๋ก ์ง๋ฏ ์ ๋ค๋ฆญ์์๋T,U,V๋ฅผ ๊ด์ต์ ์ผ๋ก ์๋๋ค. ํ์ง๋ง ๊ถ์ฅํ์ง๋ ์์ต๋๋ค. 5๋ ์ฐจ ๊ฐ๋ฐ์๋ผ๋ฉด ๊ทธ๋ฅT๋ง๊ณ ์๋ฏธ ์๋ ์ด๋ฆ(<TData>,<TResponse>)์ ์ฐ๋ ๊ฒ์ด ์ฝ๋ ๊ฐ๋ ์ฑ์ ํจ์ฌ ์ข์ต๋๋ค.
๐ซ 2. ์ ๋ค๋ฆญ ์ ์ฝ (Generic Constraints): ์๋ฌด๊ฑฐ๋ ๋ฃ์ง ๋ง!
ํ์ง๋ง ์ ๋ค๋ฆญ์ด ๋ฌด์กฐ๊ฑด ๋ซ๋ ค์๋ ๋น ์ข ์ด์ฌ์๋ ์ ๋ ๋๊ฐ ์์ต๋๋ค.
์์ฒ ์ด๊ฐ ์ ์ ์ ๊ธธ์ด๋ฅผ ์ถ๋ ฅํ๋ ์ ํธ ํจ์๋ฅผ ๋ง๋ค์๋ค๊ณ ํฉ์๋ค.
// ๐ฃ ์์ฒ : "T๋ ๋ง๋ฅ์ด๋๊น ๋ฐฐ์ด์ด๋ ๋ฌธ์์ด์ด๋ ๋ค ๋ฐ์๋ด๊ฒ ์ง!"
function logLength<T>(items: T): void {
// ๐ฃ ์๋ฌ! T๊ฐ ๋ญ์ง ๋ชจ๋ฅด๋๋ฐ length ์์ฑ์ด ์์ ์ค ์ด๋ป๊ฒ ์์?
console.log(items.length);
}์ด๋ ์ํธ ๋ฆฌ๋๊ฐ ๋ฑํํฉ๋๋ค. "์์ฒ ๋, TSํํ
์ต์ํ ์ด T๊ฐ length๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ ๊ฑด ์๋ ค์ค์ผ์ง ์๋ฌด๊ฑฐ๋ ๋ค ๋ฐ์์ฃผ๋ฉด ์ด๋กํฉ๋๊น?"
extends ๋ก ํ์
์ ์ฑ์ง ์ ํํ๊ธฐ
์ฌ๊ธฐ์์ extends๋ ํด๋์ค์ ์์์ด๋ผ๊ธฐ๋ณด๋ค "์ต์ํ ์ด ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํด!" ๋ผ๋ ๊ฒ๋ฌธ์ ์ญํ ์ ํฉ๋๋ค.
// "T๋ผ๋ ๋ณ์๋ฅผ ๋ฐ์ ๊ฑด๋ฐ, ๊ฑ๋ ๋ฌด์กฐ๊ฑด length ์์ฑ(number)์ ๊ฐ์ง ๋
์์ด์ด์ผ ํด!"
interface HasLength {
length: number;
}
function safeLogLength<T extends HasLength>(items: T): void {
// ์ด์ TS๋ T๊ฐ ๋ฌด์กฐ๊ฑด length๋ฅผ ๊ฐ์ก๋ค๊ณ ํ์ ํจ. ์๋ฌ ์ฌ๋ผ์ง!
console.log(items.length);
}
// ์ฌ์ฉ ์์
safeLogLength([1, 2, 3]); // โ
๋ฐฐ์ด์ length๊ฐ ์์
safeLogLength("์๋
ํ์ธ์"); // โ
๋ฌธ์์ด๋ length๊ฐ ์์
safeLogLength({ length: 10, name: "๋ฃฐ๋ฃจ" }); // โ
๊ฐ์ฒด ์์ length ์์ฑ์ ๋ฃ์์ผ๋ ํต๊ณผ!
// safeLogLength(123); // โ ์๋น
! ์ซ์๋ length๊ฐ ์์ต๋๋ค!์ด์ฒ๋ผ extends๋ฅผ ์ฌ์ฉํ๋ฉด, ์ ๋ค๋ฆญ์ ๋ฌดํํ ์์ ๋๋ฅผ ์์ ํ ํ
๋๋ฆฌ ์์ผ๋ก ์ขํ ์ ์์ต๋๋ค.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. interface ApiResponse<T> ์์ ๊บพ์ (<T>) ์์ ๋ค์ด๊ฐ๋ T์ ์๋ฏธ๋ฅผ ํจ์์ ํ๋ผ๋ฏธํฐ์ ๋น๊ตํ์ฌ ์ค๋ช
ํด ๋ณด์ธ์.
โ
์ ๋ต: T๋ ํจ์ ์ ์ธ๋ถ์ ๋งค๊ฐ๋ณ์(Parameter)์ ์๋ฒฝํ ๋๊ฐ์ ์ญํ ์ ํฉ๋๋ค.
๐ก ์์ธ ํด์ค:
function print(text)์์text๊ฐ ๋ฐํ์์ ์ค์ '๊ฐ'์ ๋์ค์ ๋ฐ์์ค๊ธฐ ์ํ ๋น๋ฒ์ ์ด๋ฏ์ด,<T>๋ ์ปดํ์ผ ํ์์ ์ฌ์ฉํ ์ค์ 'ํ์ '์ ๊ป๋ฐ๊ธฐ๋ฅผ ๋์ค์ ๋ฐ์์ค๊ธฐ ์ํด ๋ฏธ๋ฆฌ ์๋ฆฌ๋ฅผ ๋น์๋๋ ํ์ ๋ ๋ฒจ์ ๋งค๊ฐ๋ณ์์ผ ๋ฟ์ ๋๋ค.
Q2. ์ ๋ค๋ฆญ ํจ์๋ฅผ ์ ์ธํ ๋, ์ธ์๋ก ๋์ด์ค๋ ์ ๋ค๋ฆญ T๊ฐ ๋ฐ๋์ ํน์ ํ๋กํผํฐ(์: id)๋ฅผ ํฌํจํ๋๋ก ๊ฐ์ ํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ ํค์๋๋ ๋ฌด์์ธ๊ฐ์?
โ
์ ๋ต: extends ํค์๋์
๋๋ค. (์: <T extends { id: number }>)
๐ก ์์ธ ํด์ค:
- ์ ๋ค๋ฆญ ์ ์ฝ ์กฐ๊ฑด(Constraints)์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
- ์๋ฌด ์ ์ฝ์ด ์๋
T๋ TS ์ ์ฅ์์unknown๊ณผ ๋ค๋ฅผ ๋ฐ ์์ด ๋ด๋ถ ํ๋กํผํฐ๋ฅผ ๋ง์๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.extends๋ฅผ ํตํด ์ต์ํ์ ๊ตฌ์กฐ์ ์๊ตฌ์ฌํญ์ TS์๊ฒ ์ ๋ฌํด์ผ ํฉ๋๋ค.
Q3. [์์ฒ ์ด์ ํ
์คํธ ํ์: ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํธ๋ ์ด๋์คํ] ํ ํ์ ์๊ฐ์
๋๋ค. ์์(PM/๋ฐฑ์๋)๊ฐ "์์ฆ API ํด๋ผ์ด์ธํธ๋ก Axios ๋ง๊ณ ๋ด์ฅ fetch๋ฅผ ์ฐ์"๊ณ ์ ์ํ์ต๋๋ค. ๊ทธ๋ฌ์ ์์ฒ ์ด๊ฐ ๋ฐ๋ฐํฉ๋๋ค. "Axios๋ axios.get<User>('/api') ์ฒ๋ผ ์ ๋ค๋ฆญ์ ๋ฐ๋ก ์ง์ํด์ ํธํ๋ฐ, ์์ fetch๋ ์ ๊ฐ ๋งค๋ฒ (await res.json()) as User๋ก ์บ์คํ
ํด์ค์ผ ํ์์์! ๋๋ฌด ๋ถํธํฉ๋๋ค." ์ํธ ๋ฆฌ๋๋ ์ด ์ํฉ์ 5๋ถ ๋ง์ ์ด๋ค ์ปค์คํ
์ ํธ ํจ์๋ก ํด๊ฒฐํด ์ค ์ ์์๊น์?
โ
์ ๋ต: fetch๋ฅผ ๊ฐ์ธ๊ณ ์ ๋ค๋ฆญ <T>๋ฅผ ๋ฐ์ ์๋ต๊ฐ์ Promise<T>๋ก ๋ฐํํ๋ ๋ํผ(Wrapper) ํจ์๋ฅผ ๋ง๋ค์ด ์ค๋ค.
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ์ ๋ค๋ฆญ์ ๋ด์ฅ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ถํธํจ์ ์ฐ์ํ๊ฒ ๋ฎ๋ ํ๋ฅญํ ๋๊ตฌ์
๋๋ค. ์๋์ ๊ฐ์ ์ปค์คํ
fetch ๋ํผ๋ฅผ ๋จ ํ ๋ฒ๋ง ์ ์ธํด๋๋ฉด, ์จ ํ๋ก์ ํธ์์ Axios ๋ถ๋ฝ์ง ์๊ฒ ํ์
์ ์ฃผ์
ํ ์ ์์ต๋๋ค.
async function myFetch<T>(url: string): Promise<T> { const response = await fetch(url); if (!response.ok) throw new Error('API ๐ฃ'); return response.json(); // ์๋ฌต์ ์ผ๋ก any๊ฐ ๋ฐํ๋์ง๋ง, ๋ฐํ ํ์ ์ด Promise<T>์ด๋ฏ๋ก ์์์ ์บ์คํ ๋จ } // ์์ฒ ์ด์ ์ฌ์ฉ์ฒ: // const user = await myFetch<User>('/api/user'); - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํธํ๋ค๊ณ ์์กด์ฑ์ ๋ชฉ๋งค๋ฌ์ง ๋ง์ธ์. TypeScript์ ์ ๋ค๋ฆญ ์๋ฆฌ๋ง ์๋ฉด ์ฐ๋ฆฌ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ฒ๋ผ ๋ง๋ค์ด์ ์ฐ๋ฉด ๋ฉ๋๋ค. ์, ๋ฌผ๋ก ์ ๊ธฐ์
json()์ดany๋ฅผ ๋ฑ๋ ๊ฑด ๋ฐํ์ ๋ถ์์์์ง๋ง, Axios๋ ๊ฒฐ๊ตญ ๋๊ฐ์ ์ง์ ํ๊ณ ์์ผ๋ ์ผ๋จ ๋์ด๊ฐ์๋ค." - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๊ป๋ฐ๊ธฐ๋ ๋ด๊ฐ ๋ง๋ค ํ ๋, ์ต์ข ๊ฒฐ๊ณผ๋ฌผ(ํ์ )์ ํธ์ถํ๋ ์ชฝ(๋)์ด ๋์ ธ์ค!
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
๐ก "์ ๋ค๋ฆญ์ ์ฝ๋๊ณ์ ๋ถ์ด๋นต ๋นตํ์ด๋ค. ๋ฐ์ฃฝ์ด ํฅ์ด๋ ์ํฌ๋ฆผ์ด๋ , ๋ดํฌ(Wrapper)๋ ์ฐ์ํ๊ฒ ํํ๋ฅผ ์ ์งํด ๋ธ๋ค."
๋ด๊ฐ ๋ฉฐ์น ๋์ interface ๋ณต๋ถ ๋
ธ๊ฐ๋ค๋ฅผ ๋ฐ๋ฉฐ ์ฝ์งํ ๊ฑธ, ์ํธ ๋ฆฌ๋ ๋์ ๋จ ๋ ์ค์ ์ฝ๋๋ก ์ฐ์ํ๊ฒ ๋๋ด๋ฒ๋ ธ๋ค. <T> ํ ๊ธ์์ ์๋ ฅ์ด ์ด๋ ๊ฒ ๋ฌด์์ธ ์ค์ด์ผ.
๊ทธ๋์ ๋จ๋ค์ด ๋ง๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (React์ useState<number>()๋ axios<Res>()) ์ธ ๋ ์๋ฌด ์๊ฐ ์์ด ๊บพ์ ์์ ํ์
์ ๋์ ธ ๋ฃ์๋๋ฐ, ๊ทธ๊ฒ ๋ค ์ ๋ค๋ฆญ์ด๋ ๋
์์ด์๊ตฌ๋.
๋ด์ผ ์ถ๊ทผํ๋ฉด ๊ทธ๋์ ๋ณต๋ถํด ๋จ๋ ApiResponse_OOO ์ธํฐํ์ด์ค 40๊ฐ ์น ๋ค ์ง์ฐ๊ณ ApiResponse<T> ๋ก ํต์ผํ๋ PR ์ฌ๋ ค์ผ๊ฒ ๋ค. ๋ฆฌ๋ ๋์ด "์ค~ ์์ฒ ๋ ๋๋์ด ๊ฐ ์ก์๋ค์" ํ๊ณ ์นญ์ฐฌํด์ฃผ๋ฉด ์ข๊ฒ ๋ค ใ
ใ
ใ
ํด๊ทผ ์ฐ๋ฆฌ, ํฌ, ์! ๋ฌ๋ ค!