TypeScript generic๊ณผ keyof๋ก ํ์ ์์ ์ฑ ๋์ด๊ธฐ
TypeScript๋ก ์์
ํ๋ค ๋ณด๋ฉด ๋ค์ํ ํ์
์ ๋์ํ ์ ์๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์ถ์ ๋๊ฐ ์์ฃ . ๋ฐ๋ก ์ด๋ด ๋ ์ ๋ค๋ฆญ(Generics)์ด ํ์ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ keyof ์ฐ์ฐ์์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๊ฐ์ฒด์ ์์ฑ์ ์์ ํ๊ฒ ๋ค๋ฃฐ ์ ์์ด์. ์ด ๊ธ์์๋ TypeScript์ ์ ๋ค๋ฆญ๊ณผ keyof๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์ ์ ํจ๊ป ์ดํด๋ณผ๊ฒ์. ๊ธฐ๋ณธ ๊ฐ๋
๋ถํฐ ์ค๋ฌด์์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ํจํด๊น์ง, ๋จ๊ณ๋ณ๋ก ์ดํดํ ์ ์๋๋ก ๊ตฌ์ฑํ์ต๋๋ค.
TypeScript ์ ๋ค๋ฆญ์ ๊ธฐ์ด
์ ๋ค๋ฆญ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋๋ ํต์ฌ ๋๊ตฌ์ด๋ฉฐ ๋ค์ํ ํ์ ์ ๋์ํ๋ฉด์ ํ์ ์์ ์ฑ์ ํ๋ณดํฉ๋๋ค. ํ๋์ ํ์ ์ด ์๋ ๋ค์ํ ํ์ ์ ๋์ํ ์ ์๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ์ฃ . ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์์ ์ธ identity ํจ์๋ฅผ ๋ณผ๊น์?
function identity<T>(arg: T): T {
return arg;
}
์ฌ๊ธฐ์ <T>๋ ํ์
๋ณ์์
๋๋ค. ํจ์๋ฅผ ํธ์ถํ ๋ ์ ๋ฌ๋๋ ํ์
์ ์บก์ฒํด์, ์
๋ ฅ๊ณผ ์ถ๋ ฅ์ ํ์
์ ์ผ์น์์ผ์. ํ์
๋ณ์์ ์ด๋ฆ์ ์์ ๋กญ๊ฒ ์ ํ ์ ์์ง๋ง, ๊ด๋ก์ ์ผ๋ก T(Type์ ์ฝ์)๋ฅผ ๋ง์ด ์ฌ์ฉํฉ๋๋ค.
์ ๋ค๋ฆญ ํจ์ ํธ์ถํ๊ธฐ
์ ๋ค๋ฆญ์ ๋ช ์์ ํ์ ์ธ์ ๋๋ ํ์ ์ถ๋ก ์ผ๋ก ํธ์ถํ ์ ์์ผ๋ฉฐ, ๊ฐ๋ฅํ๋ฉด ํ์ ์ถ๋ก ์ ๊ถ์ฅํฉ๋๋ค. ์ ๋ค๋ฆญ ํจ์๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ํธ์ถํ ์ ์์ด์.
๋ช ์์ ํ์ ์ธ์ ์ ๋ฌ:
let output = identity<string>("myString");
ํ์ ์ถ๋ก ํ์ฉ:
let output = identity("myString");
๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ด ๋ ๊ฐ๊ฒฐํ๊ณ ๊ฐ๋ ์ฑ์ด ์ข์์, TypeScript๊ฐ ์๋์ผ๋ก ํ์ ์ ์ถ๋ก ํ ์ ์๋ค๋ฉด ์ด ๋ฐฉ์์ ๊ถ์ฅํฉ๋๋ค.
ํ์ ๋ณ์๊ฐ ์ฃผ๋ ์ ์ฐ์ฑ
์ ๋ค๋ฆญ์ ๋ค์ํ ํ์ ์ ์์ ํ๊ฒ ์์ฉํ๊ณ , ๋ถ์ผ์นํ ํ์ ์กฐํฉ์ ์ปดํ์ผ ํ์์ ์ฐจ๋จํฉ๋๋ค.
function toArray<T>(a: T, b: T) {
return [a, b];
}
toArray<string>("hello", "world"); // string[] ํ์
toArray<number>(1, 2); // number[] ํ์
๋ง์ฝ ์๋ก ๋ค๋ฅธ ํ์ ์ ์ธ์๋ฅผ ์ ๋ฌํ๋ฉด ์ปดํ์ผ ์์ ์ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค:
toArray<string>("hello", 1); // ํ์
์๋ฌ!
keyof ์ฐ์ฐ์๋ก ์ ๋ค๋ฆญ ์ ์ฝํ๊ธฐ
keyof๋ ๊ฐ์ฒด ํ์
์ ํค ์ ๋์จ์ ์ ๊ณตํ๊ณ , ์ ๋ค๋ฆญ ์ ์ฝ๊ณผ ๊ฒฐํฉํด ์์ฑ ์ ๊ทผ์ ํ์
์์ ํ๊ฒ ๋ง๋ญ๋๋ค. keyof ์ฐ์ฐ์๋ ๊ฐ์ฒด ํ์
์ ํค๋ค์ ๋ฆฌํฐ๋ด ํ์
์ ์ ๋์จ์ผ๋ก ๊ฐ์ ธ์ต๋๋ค. ์ด๋ฅผ ์ ๋ค๋ฆญ๊ณผ ํจ๊ป ์ฌ์ฉํ๋ฉด ๊ฐ๋ ฅํ ํ์
์ ์ฝ์ ๋ง๋ค ์ ์์ด์.
keyof์ ๊ธฐ๋ณธ ๋์
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
์ ๋ค๋ฆญ ์ ์ฝ์ผ๋ก ํ์ ์์ ์ฑ ํ๋ณดํ๊ธฐ
Key extends keyof Type ์ ์ฝ์ ํตํด ์กด์ฌํ์ง ์๋ ์์ฑ ์ ๊ทผ์ ์ปดํ์ผ ๋จ๊ณ์์ ์ฐจ๋จํ ์ ์์ต๋๋ค.
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
const person = {
name: "Anna",
age: 30
};
getProperty(person, "name"); // โ
์ ์ ๋์
getProperty(person, "email"); // โ ํ์
์๋ฌ!
ํต์ฌ ํ
์ ๋ค๋ฆญ ์ ์ฝ์ โ์ฌ์ฉ์ ํ์ฉํ ํค์ ์งํฉโ์ ํ์
์ผ๋ก ๊ฐ์ ํฉ๋๋ค. ์ฆ, ์กด์ฌํ์ง ์๋ ํค๋ ์ ์ด์ ํจ์ ์ธ์๋ก ํ์ฉ๋์ง ์์ต๋๋ค.
์์ฑ ์ค์ ํจ์ ๊ตฌํํ๊ธฐ
T[K] ์ธ๋ฑ์ค ์ ๊ทผ ํ์
์ ํ์ฉํ๋ฉด ๊ฐ์ ํ์
๊น์ง ์ ํํ ์ ์ฝํ ์ ์์ต๋๋ค.
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
const user = {
name: "John",
age: 25
};
setProperty(user, "name", "Jane"); // โ
์ ์
setProperty(user, "name", 30); // โ ํ์
์๋ฌ! (string ํ์
์ด์ด์ผ ํจ)
setProperty(user, "email", "test@test.com"); // โ ํ์
์๋ฌ! (์กด์ฌํ์ง ์๋ ์์ฑ)
์ ๋ค๋ฆญ ์ธํฐํ์ด์ค์ ํด๋์ค
ํจ์๋ฟ ์๋๋ผ ์ธํฐํ์ด์ค์ ํด๋์ค์๋ ์ ๋ค๋ฆญ์ ์ ์ฉํด ์ ์ฐํ API๋ฅผ ์ค๊ณํ ์ ์์ต๋๋ค.
์ ๋ค๋ฆญ ์ธํฐํ์ด์ค
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
์ ๋ค๋ฆญ ํด๋์ค
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
์ค๋ฌด ์์ :
class User<P> {
constructor(public payload: P) {
this.payload = payload;
}
getPayload() {
return this.payload;
}
}
const user1 = new User<{ name: string; age: number }>({
name: "Kim",
age: 28
});
const user2 = new User<string[]>(["admin", "user"]);
์ ๋ค๋ฆญ ์ ํ์ฌํญ
TypeScript์์๋ ์ ๋ค๋ฆญ enum๊ณผ namespace๋ ๋ง๋ค ์ ์์ต๋๋ค. ์ด๋ TypeScript์ ์ค๊ณ์ ์ ํ์ฌํญ์ด์์.
๊ธฐ๋ณธ ํ์ ํ๋ผ๋ฏธํฐ
์ ๋ค๋ฆญ ๊ธฐ๋ณธ๊ฐ์ ์ง์ ํ๋ฉด API ์ฌ์ฉ์ฑ์ ๋์ด๊ณ ์ผ๋ฐ ์ผ์ด์ค๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
function createHTMLElement<T extends HTMLElement = HTMLDivElement>(
element?: T
): T {
return element || (document.createElement('div') as T);
}
// ํ์
์ธ์๋ฅผ ๋ช
์ํ์ง ์์ผ๋ฉด HTMLDivElement๊ฐ ์ฌ์ฉ๋จ
const div = createHTMLElement();
// ๋ช
์์ ์ผ๋ก ๋ค๋ฅธ ํ์
์ ์ง์ ํ ์๋ ์์
const button = createHTMLElement<HTMLButtonElement>(
document.createElement('button')
);
์ค์ ์์ : ํ์ ์์ ํ ๋ฐ์ดํฐ ์ ๊ทผ ํจํด
API ์๋ต์์ ํน์ ์์ฑ์ ์์ ํ๊ฒ ์ถ์ถํ๋ ์ ํธ์ ๋ง๋ค๋ฉด ๋ฐํ์ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ํ์์ ์๋ฐฉํ ์ ์์ต๋๋ค.
interface ApiResponse {
data: {
user: {
id: number;
name: string;
email: string;
};
posts: Array<{
id: number;
title: string;
}>;
};
meta: {
timestamp: number;
version: string;
};
}
function extractData<T, K extends keyof T>(response: T, key: K): T[K] {
return response[key];
}
const response: ApiResponse = {
data: {
user: { id: 1, name: "Lee", email: "lee@example.com" },
posts: [{ id: 1, title: "First Post" }]
},
meta: {
timestamp: Date.now(),
version: "1.0"
}
};
const data = extractData(response, "data"); // data ํ์
์ด ์๋์ผ๋ก ์ถ๋ก ๋จ
const meta = extractData(response, "meta"); // meta ํ์
๋ ์ ํํ ์ถ๋ก ๋จ
์ค์ฒฉ ์์ฑ ์ ๊ทผํ๊ธฐ
์ค์ฒฉ ํค๋ฅผ ๋จ๊ณ์ ์ผ๋ก ์ ์ฝํ๋ฉด ๊น์ ๊ฒฝ๋ก ์ ๊ทผ๋ ์์ ํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
function getNestedProperty<
T,
K1 extends keyof T,
K2 extends keyof T[K1]
>(obj: T, key1: K1, key2: K2): T[K1][K2] {
return obj[key1][key2];
}
const userName = getNestedProperty(response, "data", "user"); // user ๊ฐ์ฒด ํ์
Union ํ์ ๊ณผ ์ ๋ค๋ฆญ ์ ์ฝ
extends๋ก ์ ๋์จ์ ์ง์ ํ๋ฉด ํ์ฉ ํ์
์ ์ ํํ ํ์ ํ ์ ์์ต๋๋ค.
function hello<T extends string | number>(msg: T): T {
return msg;
}
hello(3); // โ
number ํ์
hello("hi"); // โ
string ํ์
hello([3, 5]); // โ ํ์
์๋ฌ! (๋ฐฐ์ด์ ํ์ฉ๋์ง ์์)
keyof์ typeof๋ฅผ ํจ๊ป ์ฌ์ฉํ๊ธฐ
์์ ๊ฐ์ฒด๋ก๋ถํฐ ๊ฐ ์ ๋์จ ํ์
์ ์ถ์ถํ๋ ์ค๋ฌด ํจํด์ keyof์ typeof์ ์กฐํฉ์ผ๋ก ๊ตฌํํฉ๋๋ค.
const SUBJECT = {
Math: 'Math',
English: 'English',
Science: 'Science'
} as const;
type Subject = typeof SUBJECT[keyof typeof SUBJECT];
// "Math" | "English" | "Science" ํ์
function getSubjectName(subject: Subject): string {
return subject;
}
getSubjectName("Math"); // โ
์ ์
getSubjectName("History"); // โ ํ์
์๋ฌ!
์ด ํจํด์ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ๋์ํฉ๋๋ค: 1) typeof SUBJECT๋ก ๊ฐ์ฒด ํ์
์ ๊ฐ์ ธ์ด 2) keyof typeof SUBJECT๋ก ํค๋ค(โMathโ, โEnglishโ, โScienceโ)์ ์ถ์ถ 3) typeof SUBJECT[keyof typeof SUBJECT]๋ก ๊ฐ๋ค์ ๋ฆฌํฐ๋ด ํ์
์ ์ ๋์จ์ผ๋ก ๋ง๋ฆ
์ ๋ค๋ฆญ๊ณผ keyof์ ์ค๋ฌด ํ์ฉ ์ด์
ํ์ ์์ ์ฑ ํฅ์: ์ปดํ์ผ ์์ ์ ์กด์ฌํ์ง ์๋ ์์ฑ ์ ๊ทผ์ ๋ฐฉ์งํ ์ ์์ด์. ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ: ํ๋์ ํจ์๋ ํด๋์ค๋ฅผ ๋ค์ํ ํ์ ์ ๋ํด ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ ์ง๋ณด์์ฑ ๊ฐ์ : ํ์ ์ด ๋ช ํํด ๋ฆฌํฉํ ๋ง ์ ์์ ๋ง ์ญํ ์ ํฉ๋๋ค. IDE ์ง์ ๊ฐํ: ์๋์์ฑ๊ณผ ํ์ ํํธ๊ฐ ์ ํํ๊ฒ ์๋ํฉ๋๋ค.
ํ์
ํ
๋์ ํค ์ ๊ทผ ์ ํธ(์: getProperty, setProperty, getNestedProperty)์ ๋ง๋ค์ด ๋๋ฉด ํ๋ก์ ํธ ์ ๋ฐ์ ์ฌ์ฌ์ฉํ๋ฉด์ ๋ฐํ์ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ํ์์ผ๋ก ์ด๋์ํฌ ์ ์์ต๋๋ค.
์์ฃผ ๋ฌป๋ ์ง๋ฌธ (FAQ)
์ ๋ค๋ฆญ ํ์ ๋ณ์๋ ๋ช ๊ฐ๊น์ง ์ฌ์ฉํ ์ ์๋์?
ํ์ํ ๋งํผ ์ฌ์ฉ ๊ฐ๋ฅํ์ง๋ง ๊ณผ๋ํ ํ์ ๋ณ์๋ ๊ฐ๋ ์ฑ์ ํด์น๋ฏ๋ก ๋ณดํต 2~3๊ฐ๊ฐ ์ ๋นํฉ๋๋ค.
function transform<T, U, V>(input: T, mapper1: (x: T) => U, mapper2: (x: U) => V): V {
return mapper2(mapper1(input));
}
keyof๋ ์ธ์ ์ฌ์ฉํ๋ ๊ฒ ์ข์๊น์?
๋์ ์์ฑ ์ ๊ทผ, ๋งคํ/๋ณํ ์ ํธ, ํ์ ์์ ์ด๋ฒคํธ ํธ๋ค๋ฌ, ์ํ ๊ด๋ฆฌ ๋ฑ์์ ์์ฑ ์ ํจ์ฑ์ ๋ณด์ฅํ ๋ ์ ์ฉํฉ๋๋ค. ๊ฐ์ฒด์ ์์ฑ์ ์์ ํ๊ฒ ์ ๊ทผํด์ผ ํ ๋ ํนํ ํ์ ๋ฐํํฉ๋๋ค.
์ ๋ค๋ฆญ ์ ์ฝ์ด ๋๋ฌด ๋ณต์กํด์ง ๋๋ ์ด๋ป๊ฒ ํ๋์?
ํ์ ๋ณ์นญ์ผ๋ก ์ ์ฝ์ ๋ถ๋ฆฌํด ๊ฐ๋ ์ฑ์ ๋์ด์ธ์.
type ValidKey<T> = keyof T;
type ValidValue<T, K extends ValidKey<T>> = T[K];
function update<T, K extends ValidKey<T>>(
obj: T,
key: K,
value: ValidValue<T, K>
): void {
obj[key] = value;
}
ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด์ธ์
์ ๋ค๋ฆญ๊ณผ keyof๋ ํ์
์์ ์ฑ๊ณผ ์ฝ๋ ํ์ง์ ํฌ๊ฒ ํฅ์์ํค๋ ๊ฐ๋ ฅํ ๋๊ตฌ์
๋๋ค. ์์ ์ ํธ๋ฆฌํฐ๋ถํฐ ์ ์ฉ ๋ฒ์๋ฅผ ๋ํ๋ณด์ธ์. ์ค๋ฌด์์ ๊ฐ์ฒด ์์ฑ์ ์ ๊ทผํ๋ ์ฝ๋๊ฐ ์๋ค๋ฉด, ๊ทธ๊ณณ์ด ์ ๋ค๋ฆญ๊ณผ keyof๋ฅผ ์ ์ฉํ๊ธฐ ์ข์ ์์์ ์ด์์. ์ฝ๋๋ฅผ ์์ฑํ๋ค๊ฐ โ์ด ์์ฑ์ด ์ค์ ๋ก ์กด์ฌํ๋์ง ํ์ ์ด ์ ๋๋คโ๋ผ๊ณ ๋๋๋ค๋ฉด ๋ฐ๋ก ๊ทธ๋๊ฐ ์ ๋ค๋ฆญ ์ ์ฝ์ ์ถ๊ฐํ ํ์ด๋ฐ์
๋๋ค. TypeScript์ ํ์
์์คํ
์ด ์ฌ๋ฌ๋ถ์ ์ฝ๋๋ฅผ ๋ ์์ ํ๊ฒ ๋ง๋ค์ด์ค ๊ฑฐ์์ :)