🎭 인덱스 타입(Index Types)으로 객체 속성 접근하기: TypeScript의 마법 🧙♂️
안녕하세요, 여러분! 오늘은 TypeScript의 꿀잼 기능 중 하나인 '인덱스 타입(Index Types)'에 대해 알아볼 거예요. 이거 진짜 대박인 거 아시죠? ㅋㅋㅋ 객체 속성에 접근하는 방법을 완전 레벨업 시켜주는 녀석이라고요! 😎
여러분, 혹시 재능넷이라는 사이트 아세요? 다양한 재능을 거래하는 플랫폼인데, 이런 사이트를 만들 때 TypeScript를 쓰면 얼마나 편할지 상상이 가시나요? 특히 인덱스 타입을 활용하면 사용자 정보나 재능 데이터를 다룰 때 완전 꿀이에요! 자, 그럼 본격적으로 시작해볼까요? 🚀
🔑 핵심 포인트: 인덱스 타입은 TypeScript에서 객체의 속성에 동적으로 접근할 수 있게 해주는 강력한 기능이에요. 이를 통해 타입 안정성을 유지하면서도 유연한 코드를 작성할 수 있답니다!
🎨 인덱스 타입, 어떻게 생겼나요?
인덱스 타입, 처음 들으면 좀 어려워 보이죠? 근데 걱정 마세요! 생각보다 쉬워요. 그냥 객체의 키를 문자열이나 숫자로 사용할 수 있게 해주는 거예요. 예를 들어볼까요?
interface UserInfo {
name: string;
age: number;
[key: string]: string | number;
}
const user: UserInfo = {
name: "김철수",
age: 25,
hobby: "코딩",
favoriteNumber: 42
};
여기서 [key: string]: string | number; 이 부분이 바로 인덱스 시그니처예요. 이게 뭐냐고요? 쉽게 말해서 "어떤 문자열 키든 다 받아들일 수 있고, 그 값은 문자열이나 숫자일 수 있어!"라고 TypeScript한테 말해주는 거예요. 완전 자유로워! ㅋㅋㅋ
💡 꿀팁: 인덱스 시그니처를 사용하면 객체에 원하는 만큼 속성을 추가할 수 있어요. 마치 재능넷에서 사용자가 자신의 다양한 재능을 프로필에 추가하는 것처럼요!
🎭 인덱스 타입의 마법: 키오브(keyof) 연산자
자, 이제 진짜 재미있는 부분이 나와요! 'keyof' 연산자를 소개합니다. 이 녀석은 정말 대단해요. 객체 타입의 모든 키를 유니온 타입으로 만들어주거든요. 뭔 소리냐고요? 예제를 보면 바로 이해될 거예요!
interface Talent {
singing: number;
dancing: number;
acting: number;
}
type TalentKey = keyof Talent; // "singing" | "dancing" | "acting"
function improveTalent(person: Talent, skill: TalentKey): void {
person[skill]++;
}
let idol: Talent = {
singing: 8,
dancing: 9,
acting: 7
};
improveTalent(idol, "dancing"); // OK
improveTalent(idol, "cooking"); // 에러! "cooking"은 Talent의 키가 아니에요.
여기서 keyof Talent는 "singing", "dancing", "acting" 중 하나만 선택할 수 있게 해줘요. 마치 재능넷에서 사용자가 자신의 재능 중 하나를 선택해서 향상시키는 것처럼요! 😉
🔍 인덱스 접근 타입: 객체의 속성 타입 가져오기
이번엔 더 신기한 걸 보여드릴게요! 인덱스 접근 타입을 사용하면 객체의 특정 속성의 타입을 가져올 수 있어요. 이게 무슨 말이냐고요? 예제를 보면 바로 이해될 거예요!
interface User {
id: number;
name: string;
email: string;
}
type UserEmail = User["email"]; // string 타입
function sendEmail(email: UserEmail) {
console.log(`이메일 전송: ${email}`);
}
sendEmail("user@example.com"); // OK
sendEmail(123); // 에러! number는 string 타입이 아니에요.
여기서 User["email"]은 User 인터페이스의 email 속성의 타입을 가져와요. 즉, string 타입이 되는 거죠. 이렇게 하면 타입을 재사용할 수 있어서 정말 편리해요! 😊
🚀 응용 팁: 이런 기능을 활용하면 재능넷 같은 사이트에서 사용자 정보를 다룰 때 타입 안정성을 높일 수 있어요. 예를 들어, 이메일 전송 함수에는 반드시 올바른 이메일 타입만 전달되도록 할 수 있죠!
🎡 매핑된 타입: 기존 타입을 변형시키기
이제 정말 고급 기술로 들어갑니다! 매핑된 타입을 사용하면 기존 타입을 기반으로 새로운 타입을 만들 수 있어요. 이게 바로 TypeScript의 진정한 힘이죠! ㅋㅋㅋ
type Readonly<t> = {
readonly [P in keyof T]: T[P];
};
interface Skill {
name: string;
level: number;
}
type ReadonlySkill = Readonly<skill>;
let mySkill: ReadonlySkill = {
name: "프로그래밍",
level: 8
};
mySkill.level = 9; // 에러! 읽기 전용 속성이에요.
</skill></t>
여기서 Readonly
🎭 조건부 타입: 타입의 if-else 문
자, 이제 진짜 고급 스킬이 나옵니다! 조건부 타입을 사용하면 조건에 따라 다른 타입을 선택할 수 있어요. 이건 마치 타입 시스템에서의 if-else 문과 같아요!
type IsString<t> = T extends string ? true : false;
type Check1 = IsString<"hello">; // true
type Check2 = IsString<42>; // false
type ExtractArray<t> = T extends Array<infer u> ? U : never;
type NumberArray = ExtractArray<number>; // number
type StringArray = ExtractArray<string>; // string
type MixedArray = ExtractArray<(number | string)[]>; // number | string
</string></number></infer></t></t>
여기서 T extends string ? true : false는 "T가 string 타입을 확장하면 true, 아니면 false"라는 의미예요. 그리고 T extends Array
🔥 고급 팁: 조건부 타입을 활용하면 재능넷에서 사용자의 재능 유형에 따라 다른 처리를 할 수 있어요. 예를 들어, 음악 관련 재능과 프로그래밍 관련 재능을 다르게 처리할 수 있죠!
🌈 인덱스 타입의 실전 응용: 폼 유효성 검사
자, 이제 우리가 배운 걸 실제로 어떻게 쓸 수 있는지 알아볼까요? 예를 들어, 폼 유효성 검사를 할 때 인덱스 타입을 사용하면 정말 편리해요!
interface ValidationRules {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
}
interface FormFields {
username: ValidationRules;
email: ValidationRules;
password: ValidationRules;
}
const formValidationRules: FormFields = {
username: { required: true, minLength: 3, maxLength: 20 },
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
password: { required: true, minLength: 8 }
};
function validate<t extends keyof formfields>(
field: T,
value: string,
rules: FormFields[T]
): boolean {
if (rules.required && !value) {
console.log(`${field} is required`);
return false;
}
if (rules.minLength && value.length < rules.minLength) {
console.log(`${field} should be at least ${rules.minLength} characters`);
return false;
}
if (rules.maxLength && value.length > rules.maxLength) {
console.log(`${field} should be no more than ${rules.maxLength} characters`);
return false;
}
if (rules.pattern && !rules.pattern.test(value)) {
console.log(`${field} does not match the required pattern`);
return false;
}
return true;
}
// 사용 예
validate("username", "john", formValidationRules.username); // true
validate("email", "invalid-email", formValidationRules.email); // false
validate("password", "123", formValidationRules.password); // false
</t>
이 예제에서 T extends keyof FormFields를 사용해서 field 매개변수가 FormFields의 키 중 하나여야 한다고 지정했어요. 그리고 FormFields[T]를 사용해서 해당 필드의 유효성 검사 규칙을 가져왔죠. 이렇게 하면 타입 안정성을 유지하면서도 유연한 코드를 작성할 수 있어요! 👏
💡 실용적인 팁: 이런 방식으로 재능넷에서 사용자 등록 폼이나 재능 등록 폼의 유효성을 검사할 수 있어요. 각 필드마다 다른 규칙을 적용하면서도, 타입 안정성을 유지할 수 있죠!
🏆 인덱스 타입의 고급 기술: 재귀적 타입
이제 정말 고급 스킬로 들어갑니다! 재귀적 타입을 사용하면 복잡한 중첩 구조도 타입 안전하게 다룰 수 있어요. 이건 정말 대박이에요! ㅋㅋㅋ
type NestedObject<t> = {
[K in keyof T]: T[K] extends object ? NestedObject<t> : T[K];
};
interface DeepUser {
name: string;
age: number;
address: {
street: string;
city: string;
country: {
name: string;
code: string;
};
};
hobbies: string[];
}
type DeepReadonly<t> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<t> : T[K];
};
const user: DeepReadonly<deepuser> = {
name: "김철수",
age: 30,
address: {
street: "강남대로",
city: "서울",
country: {
name: "대한민국",
code: "KR"
}
},
hobbies: ["코딩", "독서"]
};
// 다음 코드는 에러를 발생시킵니다!
// user.name = "이영희"; // 에러: 읽기 전용 속성이므로 'name'에 할당할 수 없습니다.
// user.address.city = "부산"; // 에러: 읽기 전용 속성이므로 'city'에 할당할 수 없습니다.
// user.address.country.code = "US"; // 에러: 읽기 전용 속성이므로 'code'에 할당할 수 없습니다.
// user.hobbies.push("여행"); // 에러: 'readonly string[]' 형식에 'push' 속성이 없습니다.
</deepuser></t></t></t></t>
여기서 DeepReadonly
🎨 인덱스 타입과 유니온 타입의 조합
이제 인덱스 타입과 유니온 타입을 조합해서 더 강력한 타입을 만들어볼까요? 이건 정말 대박 기능이에요! ㅋㅋㅋ
type Skill = "programming" | "design" | "marketing";
type SkillLevel = "beginner" | "intermediate" | "expert";
type UserSkills = {
[K in Skill]?: SkillLevel;
};
const user: UserSkills = {
programming: "expert",
design: "intermediate"
};
function improveSkill(user: UserSkills, skill: Skill): UserSkills {
const currentLevel = user[skill];
let newLevel: SkillLevel;
switch (currentLevel) {
case "beginner":
newLevel = "intermediate";
break;
case "intermediate":
newLevel = "expert";
break;
case "expert":
newLevel = "expert";
break;
default:
newLevel = "beginner";
}
return { ...user, [skill]: newLevel };
}
const updatedUser = improveSkill(user, "design");
console.log(updatedUser); // { programming: "expert", design: "expert" }
여기서 [K in Skill]?: SkillLevel;는 Skill 유니온의 각 값을 키로 사용하고, 그 값으로 SkillLevel을 가지는 객체 타입을 만들어요. 이렇게 하면 사용자의 스킬을 타입 안전하게 관리할 수 있죠! 👍
🌟 창의적인 팁: 이런 방식을 활용하면 재능넷에서 사용자의 다양한 재능과 그 수준을 효과적으로 관리할 수 있어요. 각 재능마다 레벨을 설정하고, 향상시킬 수 있는 기능을 쉽게 구현할 수 있죠!
🎭 인덱스 타입과 제네릭의 환상적인 콜라보
자, 이제 정말 고급 스킬의 끝판왕이 나옵니다! 인덱스 타입과 제네릭을 조합하면 정말 놀라운 일을 할 수 있어요. 이건 진짜 TypeScript의 파워를 제대로 보여주는 거예요! ㅋㅋㅋ
type PropType<t path extends string> =
Path extends keyof T
? T[Path]
: Path extends `${infer K}.${infer R}`
? K extends keyof T
? PropType<t r>
: never
: never;
interface DeepNestedObject {
a: {
b: {
c: {
d: number;
e: string;
};
f: boolean;
};
g: string[];
};
h: Date;
}
type Test1 = PropType<deepnestedobject>; // { b: { c: { d: number; e: string; }; f: boolean; }; g: string[]; }
type Test2 = PropType<deepnestedobject>; // number
type Test3 = PropType<deepnestedobject>; // string[]
type Test4 = PropType<deepnestedobject>; // Date
function getNestedProp<t p extends string>(obj: T, path: P): PropType<t p> {
return path.split('.').reduce((o, k) => o && o[k], obj ) as PropType<t p>;
}
const deepObj: DeepNestedObject = {
a: {
b: {
c: {
d: 42,
e: "Hello"
},
f: true
},
g: ["TypeScript", "JavaScript"]
},
h: new Date()
};
const value1 = getNestedProp(deepObj, "a.b.c.d"); // 42
const value2 = getNestedProp(deepObj, "a.g"); // ["TypeScript", "JavaScript"]
const value3 = getNestedProp(deepObj, "h"); // Date 객체
console.log(value1, value2, value3);
</t></t></t></deepnestedobject></deepnestedobject></deepnestedobject></deepnestedobject></t></t>
와우! 이 코드는 정말 대단해요.