๐ฆ 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'); - ๋ค๋ง ์ด ๋ํผ๋ ๋ฐํ์ ๊ฒ์ฆ์ ์๋์ผ๋ก ํด์ฃผ์ง๋ ์์ต๋๋ค. 1์ฅ์์ ๋ณธ ๊ฒ์ฒ๋ผ
response.json()์ ์ค์ ๋ชจ์์ ๋คํธ์ํฌ ๊ฒฝ๊ณ์์ ๋ณ๋๋ก ํ์ธํด์ผ ํฉ๋๋ค. - ํต์ฌ์ ๊ณตํต ๋ดํฌ์ ํธ์ถ๋ถ๋ณ ๋ฐ์ดํฐ ํ์
์ ๋ถ๋ฆฌํ๋ ๊ฒ์
๋๋ค. ๋ดํฌ๋ ๋ํผ๊ฐ ์ฑ
์์ง๊ณ , ์ต์ข
๋ฐ์ดํฐ ํ์
์ ํธ์ถํ๋ ์ชฝ์์
<User>์ฒ๋ผ ๋ช ํํ ๋๊น๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ์ ๋ค๋ฆญ์ด "์ด๋ ค์ด ๋ฌธ๋ฒ"์ด๋ผ๊ธฐ๋ณด๋ค ํ์
์ธ๊ณ์ ๋งค๊ฐ๋ณ์๋ผ๋ ๊ฒ ์ ๋ช
ํด์ก๋ค. ApiResponse<T> ํ๋๋ก ๊ณตํต ์๋ต ๋ดํฌ๋ฅผ ๊ณ ์ ํ๊ณ , ์์ชฝ data๋ง ํธ์ถ๋ถ๋ง๋ค ๋ฐ๊พธ๋ฉด ๋ณต๋ถ ํ์
์ด ์ฌ๋ผ์ง๋ค.
extends ์ ์ฝ๋ ๋ง์์ ๋จ์๋ค. ์๋ฌด ํ์
์ด๋ ๋ฐ๋ ์์ ๋ณด๋ค, ์ต์ํ์ ์กฐ๊ฑด์ ๊ฑธ์ด๋๋ ์์ ๊ฐ ๋ ์ค๋ฌด์ ์ด์๋ค. length๊ฐ ํ์ํ๋ฉด T extends { length: number }์ฒ๋ผ ์ปดํ์ผ๋ฌ๊ฐ ์ดํดํ ๊ทผ๊ฑฐ๋ฅผ ์ค์ผ ํ๋ค.
๐ก "์ ๋ค๋ฆญ์ ์ค๋ณต ํ์ ์ ์ง์ฐ๋ ๋๊ตฌ์ด์ง๋ง, ์ ์ฝ ์๋ ์ ๋ค๋ฆญ์ ์ฑ ์ ์๋ ๋น์นธ์ด ๋๋ค. ๋ณํ๋ ๋ถ๋ถ๊ณผ ๋ฐ๋์ ํ์ํ ์กฐ๊ฑด์ ํจ๊ป ํํํด์ผ ํ๋ค."
๋ด์ผ์ ApiResponse_User์ฒ๋ผ ์ด๋ฆ๋ง ๋ค๋ฅธ ์ธํฐํ์ด์ค๋ฅผ ApiResponse<TData>๋ก ์ ๋ฆฌํ๋ PR์ ์๊ฒ ์ฌ๋ฆด ์๊ฐ์ด๋ค. ํ ๋ฒ์ ์ ๋ถ ๋ฐ๊พธ๊ธฐ๋ณด๋ค ํธ์ถ๋ถ ํ์
์ถ๋ก ์ด ๊นจ์ง๋ ๊ณณ์ ํ์ธํ๋ฉด์ ์ฒ์ฒํ ๋ฌถ์ด์ผ๊ฒ ๋ค.