📘 2025.03.22 TypeScript 정리
@2025.03.29 곽지혜
📑 주제
- 조건부 타입 / 맵드 타입
- 제네릭 심화 (제약 조건, 기본값, 다중 파라미터)
- 타입 조작: Exclude, Extract, NonNullable, Partial 등
📂 파일 구조 (예제 위치)
src/
├── 01_conditional_types.ts
├── 02_mapped_types.ts
├── 03_generics_advanced.ts
├── 04_utility_types.ts
├── 05_examples_comparison.ts
1. 조건부 타입 (Conditional Types)
📁 예제: 01_conditional_types.ts
T extends U ? X : Y
조건에 따라 타입을 분기할 수 있습니다.
T가 U를 확장(extends) 하면 타입은 X, 아니면 Y가 됩니다.
- 조건부 타입 문법에는
extends
키워드와 물음표?
기호를 사용합니다. (삼항 연산자) - 자바스크립트의 삼항 연산자는 변수의 값을 조건에 따라 결정한다면, 타입스크립트의 조건부 타입은 값 대신 타입을 조건에 따라 결정합니다.
- 조건부 타입도 유니온처럼 하나의 타입입니다.
- 조건부 타입을 중첩해서 쓰면 가독성이 매우 안좋아지므로 고려해야합니다.
2. 맵드 타입 (Mapped Types)
📁 예제: 02_mapped_types.ts
객체의 키를 순회하며 타입을 재구성할 수 있습니다.
객체 타입을 변형하는데 사용되며, keyof
, in
와 함께 사용됩니다.
keyof
: 객체 형태의 타입을, 따로 속성들만 뽑아 모아 유니온 타입으로 만들어주는 연산자in
: 객체에 특성 속성이 존재하는지 확인하는데 사용
// 단순한 타입: 두개의 필드를 가진 Person 객체
type Person = { name: string; age: number };
// 변형된 타입
type OptionalPerson = {
[K in keyof Person]?: Person[K];
};
// 결과적으로 생성된 타입
type OptionalPerson = {
name?: string;
age?: number;
};
// 즉, Person의 모든 필드를 Optional한 버전으로 생성
const someone: OptionalPerson = {};
const another: OptionalPerson = { name: "Tom" };
keyof Person
: Person 타입의 키를 유니온으로 추출[K in keyof Person]
: name, age를 K에 넣어 반복, 즉 Ksms name->age 두 번 순회?:
와Person[K]
:K
는 name 또는 age니까 Person[K]는 각각 strng, number가 됨?:
는 해당 필드를 optional로 만들어줍니다.
3. 제네릭 고급 문법
제네릭 타입은 함수나 클래스의 선언 시점이 아닌, 사용 시점에 타입을 선언할 수 있는 방법을 제공합니다.
- 타입이 고정되는 것을 방지하고 재사용 가능한 요소를 선언할 수 있다.
- 타입 검사를 컴파일 시간에 진행함으로써 타입 안정성을 보장.
- 캐스팅 관련 코드를 제거할 수 있다.
- 제네릭 로직을 이용해 타입을 다르게 받을 수 있는 재사용 코드를 만들 수 있다.
꺾쇠 <> 기호를 변수명, 함수명 앞에다 쓰면 '타입 단언' 이 되게 됩니다.
따라서 제네릭을 구현하려면 변수명, 함수명 뒤에다가 꺾쇠 괄호를 적어주어야 합니다.
제네릭명은 꼭 T가 아니어도 됩니다.
제네릭에서 T
를 많이 사용하는 데에는 관습적인 이유와 의미적인 이유가 있습니다.
T
는"Type"
의 약자로 많이 쓰이기 때문에, 타입을 나타내는 변수명으로T
를 지정하는 게 일반적입니다.- 타입스크립트에서 타입을 유연하게 다룰 때 제네릭을 사용하는데, 그때
T
,U
,V
,K
등이 자주 사용됩니다.
🔍 왜 T를 사용할까?
1. 의미적인 이유 (Type의 약자)
제네릭은 기본적으로 타입을 유연하게 다루기 위해 사용됩니다.
그래서 타입을 표현하는 변수명으로 T
를 쓰는 것이 직관적입니다.
type ApiResponse<T> = {
data: T;
success: boolean;
};
- 위 코드에서
T
는 타입을 받는 제네릭 변수로, "타입"이라는 의미를 명확히 전달합니다.
2. 관습적인 이유
T
는 TypeScript, Java 등의 제네릭 예제에서도 널리 사용되기 때문에
많은 개발자들이 자연스럽게 T
를 사용하게 됩니다.
// T: 첫 번째 타입, U: 두 번째 타입
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
T
와U
는 각각 a, b의 타입을 표현하며, 결과는T & U
타입이 됩니다.
3. 제네릭 이름은 자유롭게 설정 가능
T
는 관습일 뿐이며, 다음과 같이 의미 있는 이름으로 대체할 수도 있습니다.
type ApiResponse<ResponseType> = {
data: ResponseType;
success: boolean;
};
🔑 결론
T
는"Type"
을 의미하고, 제네릭 변수로 가장 일반적으로 사용- 하지만
T
,U
외에도 다음과 같은 표현이 자주 쓰입니다:
이름 | 의미 |
---|---|
K |
Key (속성명) |
V |
Value (값) |
R |
Return (리턴값) |
3.1 제약 조건 (Constraint)
📁 예제: 03_generics_advanced.ts
extends
를 사용하여 제네릭이 특정 타입을 따라야 함을 명시합니다.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
3.2 기본값 (Default)
제네릭(Generic) 타입은 타입 인자를 명시하지 않으면 오류가 나는 경우가 있는데,
기본값(Default Type Parameter)을 지정하면 타입 인자를 생략해도 기본값이 자동으로 적용됩니다.
기본값은 T = 기본값처럼 제네릭 이름 오른쪽에 =를 붙여 설정합니다.
type ApiResponse<T = any> = {
data: T;
success: boolean;
};
const res: ApiResponse = { data: "hello", success: true }; // T는 any
- 이 코드는 제네릭 타입 T를 받되, 타입 인자를 생략했을 경우 기본값으로 any가 설정됩니다.
- 필요할 때 명시하고, 아닐 때는 기본값으로 동작하는 유연한 구조를 만들 수 있습니다.
3.3 다중 타입 파라미터
제네릭을 여러 개 받을 수 있습니다.
타입을 파라미터처럼 받아서 유연하게 코드를 작성할 수 있게 합니다.
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
// T, U라는 두 개의 타입 파라미터를 받아서 두 객체 a와 b를 합친 결과를 T & U라는 타입으로 리턴
const result = merge({ name: "Tom" }, { age: 30 });
// 타입: { name: string; age: number }
T
는{ name: string }
U
는{ age: number }
T & U
는{ name: string; age: number }
→ 즉, 두 객체를 합친 타입이 결과로 추론됩니다.
💡 왜 제네릭을 두 개 이상 받는가?
단일 제네릭만으로는 두 인자의 타입이 서로 다른 상황을 표현하기 어려습니다.
❌ 단일 제네릭으로 하면 이런 문제 발생
function merge<T>(a: T, b: T): T {
return { ...a, ...b };
}
const m = merge({ name: "Tom" }, { age: 30 }); // ❌ 에러
- 이 경우
a
,b
가 같은 타입이어야 하기 때문에 에러가 발생합니다.
제네릭은 타입을 "파라미터처럼" 받아서 유연하게 코드를 작성할 수 있게 해주는 도구입니다.
그리고 **여러 타입이 동시에 필요하다면, 제네릭도 여러 개 받을 수 있습니다.
요약: 다중 타입 파라미터 언제 쓰나요?
상황 | 제네릭 개수 | 예시 |
---|---|---|
같은 타입을 여러 곳에 반복 사용 | 1개 | function identity<T>(value: T): T |
서로 다른 타입의 인자를 다뤄야 할 때 | 2개 이상 | function merge<T, U>(a: T, b: U): T & U |
3개 이상도 가능 | 필요에 따라 | function zip<T, U, R>() 등 |
4. 타입 유틸리티 (Utility Types)
📁 예제: 04_utility_types.ts
Exclude<T, U>
: T에서 U를 제거한 타입을 반환Extract<T, U>
: T와 U의 공통 타입만 추출NonNullable<T>
: null과 undefined를 제외
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T2 = Extract<"a" | "b", "a" | "z">; // "a"
type T3 = NonNullable<string | null>; // string
기타 유틸리티:
- Partial, Required, Readonly, Record<K, T>
5. 실전 예제 비교
📁 예제: 05_examples_comparison.ts
초보자 스타일 vs 제네릭 + 유틸리티 적용 예시 비교:
// 초보자
function fetchUser(): { name: string } { return { name: "Tom" }; }
// 개선
function fetchData<T>(url: string): Promise<T> {
return fetch(url).then(res => res.json());
}
6. 요약 및 정리
기능 | 초보 코드 | 고급 코드 |
---|---|---|
중복 제거 | 함수/타입 반복 | 제네릭 하나로 처리 |
타입 안정성 | any 남용 위험 | 제약조건 + 유틸리티 활용 |
유지보수 | 새로운 상황마다 새 타입 | 기존 타입 기반으로 파생 |
자동완성 | X | O (IDE 도움) |
🔥 난이도 & 사용 빈도 분류
항목 | 설명 | 사용 빈도 | 난이도 |
---|---|---|---|
조건부 타입 | 삼항 연산자 형태 타입 분기 | ⭐⭐⭐ | 🔥🔥🔥 |
맵드 타입 | 객체 키 기반 타입 생성 | ⭐⭐⭐⭐ | 🔥🔥 |
제네릭 기본 | 기본 사용 | ⭐⭐⭐⭐ | 🔥 |
유틸리티 타입 | Partial, Omit 등 | ⭐⭐⭐⭐ | 🔥 |
출처
'Development > React' 카테고리의 다른 글
[React] 상태 (State) (0) | 2024.02.28 |
---|---|
[React] JSX 사용하기 (0) | 2024.02.28 |
[React] Datepicker 사용하기 (0) | 2023.05.02 |
[React] 리액트 프로젝트 생성하기 (0) | 2023.03.13 |