고급 타입
1. 고급 타입
1.1 Enum 타입
상수 집합을 정의할 때 사용하는 타입입니다. 다른 언어에서도 종종 사용되며, 주로 숫자나 문자열 값으로 이루어진 집합을 정의할 때 사용합니다. 기본적으로 0부터 시작하며, 처음 시작 값을 변경하면 그 다음 값부터는 순차적으로 1씩 증가합니다. 이렇게 선언된 Enum은 안에 있는 값을 변경할 수 없습니다.
1.1.1 숫자형 Enum
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
console.log(Direction.Up); // 1
console.log(Direction.Down); // 2
console.log(Direction.Left); // 3
console.log(Direction.Right); // 4
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
console.log(Direction.Up); // 1
console.log(Direction.Down); // 2
console.log(Direction.Left); // 3
console.log(Direction.Right); // 4
1.1.2 문자열 Enum
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING"
}
console.log(Status.Active); // ACTIVE
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING"
}
console.log(Status.Active); // ACTIVE
1.2 유틸리티 타입
TypeScript에서 제공하는 유용한 타입 변환 도구들입니다. 우선 아래 코드를 살펴보도록 하겠습니다.
type Autor = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type Notice = {
title: string;
content: string;
author: Autor;
}
const notice: Notice = {
title: '공지사항',
content: '내용',
author: {
name: '홍길동',
age: 30,
password: '1234',
birth: '1990-01-01',
phone: '010-1234-5678',
email: 'licat@gamil.com'
}
}
console.log(notice);
type Autor = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type Notice = {
title: string;
content: string;
author: Autor;
}
const notice: Notice = {
title: '공지사항',
content: '내용',
author: {
name: '홍길동',
age: 30,
password: '1234',
birth: '1990-01-01',
phone: '010-1234-5678',
email: 'licat@gamil.com'
}
}
console.log(notice);
이 코드에 문제는 내가 게시물을 작성하고 싶은데, 모든 정보를 입력해야 한다는 것입니다. 이 문제를 아래 유틸리티 타입을 사용하여 해결할 수 있습니다.
1.2.1 Partial
모든 속성을 선택적으로 만듭니다.
type Autor = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type partialAutor = Partial<Autor>;
const author: partialAutor = {
name: 'licat',
age: 30
}
const authorEmpty: partialAutor = {};
console.log(author);
console.log(authorEmpty);
type Autor = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type partialAutor = Partial<Autor>;
const author: partialAutor = {
name: 'licat',
age: 30
}
const authorEmpty: partialAutor = {};
console.log(author);
console.log(authorEmpty);
1.2.2 Pick
특정 속성만 선택합니다. Pick 된 것 외에 더 넣거나 덜 넣으면 애러가 발생합니다.
type Author = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type pickAuthor = Pick<Author, 'name' | 'age'>;
const author: pickAuthor = {
name: 'licat',
age: 30
}
console.log(author);
type Author = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type pickAuthor = Pick<Author, 'name' | 'age'>;
const author: pickAuthor = {
name: 'licat',
age: 30
}
console.log(author);
1.2.3 Omit
특정 속성을 제외합니다.
type Author = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type omitAuthor = Omit<Author, 'age' | 'password' | 'birth' | 'phone' | 'email'>;
const autor: omitAuthor = {
name: 'licat'
}
console.log(autor);
type Author = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type omitAuthor = Omit<Author, 'age' | 'password' | 'birth' | 'phone' | 'email'>;
const autor: omitAuthor = {
name: 'licat'
}
console.log(autor);
1.2.4 문제 해결
처음에 언급했던 코드에 문제를 해결해보도록 하겠습니다.
type Autor = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type Notice = {
title: string;
content: string;
author: Pick<Autor, 'name'>;
}
const notice: Notice = {
title: '공지사항',
content: '내용',
author: {
name: 'licat'
}
}
console.log(notice);
type Autor = {
name: string;
age: number;
password: string;
birth: string;
phone: string;
email: string;
}
type Notice = {
title: string;
content: string;
author: Pick<Autor, 'name'>;
}
const notice: Notice = {
title: '공지사항',
content: '내용',
author: {
name: 'licat'
}
}
console.log(notice);
1.3 unknown 타입
unknown
은 타입 안전성을 강화한 any
타입입니다. 타입을 확정하기 전까지는 값을 사용할 수 없습니다.
let value: unknown = 4;
value = "hello";
// 타입 검사 없이 사용 불가
// console.log(value.length); // 에러!
// 타입 검사 후 사용 가능
if (typeof value === "string") {
console.log(value.length); // 정상 동작
}
let value: unknown = 4;
value = "hello";
// 타입 검사 없이 사용 불가
// console.log(value.length); // 에러!
// 타입 검사 후 사용 가능
if (typeof value === "string") {
console.log(value.length); // 정상 동작
}
1.4 맵드 타입
맵드 타입(Mapped Types)은 기존 타입의 모든 속성을 새로운 규칙에 따라 변환하여 새로운 타입을 만드는 방법입니다. 마치 배열의 map 함수처럼, 타입의 각 속성을 다른 형태로 변환할 수 있습니다.
1.4.1 기본 개념
// 기본 인터페이스
interface User {
name: string; // 필수
age: number; // 필수
email: string; // 필수
}
// 모든 속성을 선택적(optional)으로 만드는 맵드 타입
type Optional<T> = {
[P in keyof T]?: T[P];
};
// 사용 예시
type OptionalUser = Optional<User>;
// OptionalUser는 다음과 같은 형태가 됩니다:
// {
// name?: string; // 선택적
// age?: number; // 선택적
// email?: string; // 선택적
// }
// 실제 사용
const user1: User = {
name: "licat", // 모든 속성이 필수
age: 10,
email: "kim@mail.com"
};
const user2: OptionalUser = {
name: "mura" // 일부 속성만 사용 가능
}; // OK! - age와 email이 선택적이므로 생략 가능
// 기본 인터페이스
interface User {
name: string; // 필수
age: number; // 필수
email: string; // 필수
}
// 모든 속성을 선택적(optional)으로 만드는 맵드 타입
type Optional<T> = {
[P in keyof T]?: T[P];
};
// 사용 예시
type OptionalUser = Optional<User>;
// OptionalUser는 다음과 같은 형태가 됩니다:
// {
// name?: string; // 선택적
// age?: number; // 선택적
// email?: string; // 선택적
// }
// 실제 사용
const user1: User = {
name: "licat", // 모든 속성이 필수
age: 10,
email: "kim@mail.com"
};
const user2: OptionalUser = {
name: "mura" // 일부 속성만 사용 가능
}; // OK! - age와 email이 선택적이므로 생략 가능
1.4.2 자주 사용하는 맵드 타입 예시
아래와 같이 사용하면 접두사를 추가하는 맵드 타입을 만들 수 있습니다.
type Notice = {
title: string;
content: string;
date: string;
}
type TestNotice<T> = {
[P in keyof T as `hello${string & P}`]: string;
};
const testNotice: TestNotice<Notice> = {
hellotitle: '타입스크립트',
hellocontent: '여긴 어디죠? 나는 누구인가요?',
hellodate: '2024-13-30'
}
console.log(testNotice);
type Notice = {
title: string;
content: string;
date: string;
}
type TestNotice<T> = {
[P in keyof T as `hello${string & P}`]: string;
};
const testNotice: TestNotice<Notice> = {
hellotitle: '타입스크립트',
hellocontent: '여긴 어디죠? 나는 누구인가요?',
hellodate: '2024-13-30'
}
console.log(testNotice);
모든 타입을 필수 타입으로 변경하는 것도 가능합니다.
type OptionalNotice = {
title?: string; // 선택적
content?: string; // 선택적
date: string; // 필수
}
type RequiredTestNotice<T> = {
[P in keyof T as `hello${string & P}`]-?: string;
};
const testNotice: RequiredTestNotice<OptionalNotice> = {
hellotitle: '타입스크립트',
hellocontent: '여긴 어디죠? 나는 누구인가요?',
hellodate: '2024-13-30'
// 모든 필드가 필수가 됩니다.
// hellotitle 생략 시 에러
// hellocontent 생략 시 에러
// hellodate 생략 시 에러
}
console.log(testNotice);
type OptionalNotice = {
title?: string; // 선택적
content?: string; // 선택적
date: string; // 필수
}
type RequiredTestNotice<T> = {
[P in keyof T as `hello${string & P}`]-?: string;
};
const testNotice: RequiredTestNotice<OptionalNotice> = {
hellotitle: '타입스크립트',
hellocontent: '여긴 어디죠? 나는 누구인가요?',
hellodate: '2024-13-30'
// 모든 필드가 필수가 됩니다.
// hellotitle 생략 시 에러
// hellocontent 생략 시 에러
// hellodate 생략 시 에러
}
console.log(testNotice);
2. 연습문제
2.1 유틸리티 타입 활용하기
아래 인터페이스를 기반으로 유틸리티 타입을 활용해보세요.
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
- id 제외하는 interface를 만들어주세요.
- 모든 필드가 선택적이 되는 interface를 만들어주세요.
- id, name, price만 포함이 되는 interface를 만들어주세요.
2.2 맵드 타입 구현하기
아래 인터페이스 기반으로 다음과 같은 맵드 타입을 구현해보세요.
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
}
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
}
- 모든 속성을 읽기 전용으로 만드는
Readonly<T>
구현 - 모든 속성을 필수로 만드는
Required<T>
구현
3. 연습문제 정답
3.1 유틸리티 타입 활용하기
3.1.1 id를 제외
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
// Omit 유틸리티 타입을 사용하여 id 필드를 제외
type ProductWithoutId = Omit<Product, 'id'>;
// 사용 예시
const product: ProductWithoutId = {
name: "노트북",
price: 1000000,
description: "고성능 노트북",
category: "전자기기"
};
console.log(product);
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
// Omit 유틸리티 타입을 사용하여 id 필드를 제외
type ProductWithoutId = Omit<Product, 'id'>;
// 사용 예시
const product: ProductWithoutId = {
name: "노트북",
price: 1000000,
description: "고성능 노트북",
category: "전자기기"
};
console.log(product);
3.1.2 모든 필드 선택
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
// Partial 유틸리티 타입을 사용하여 모든 필드를 선택적으로 만듦
type OptionalProduct = Partial<Product>;
// 사용 예시
const partialProduct: OptionalProduct = {
name: "키보드",
price: 50000
// 다른 필드는 선택적이므로 생략 가능
};
console.log(partialProduct);
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
// Partial 유틸리티 타입을 사용하여 모든 필드를 선택적으로 만듦
type OptionalProduct = Partial<Product>;
// 사용 예시
const partialProduct: OptionalProduct = {
name: "키보드",
price: 50000
// 다른 필드는 선택적이므로 생략 가능
};
console.log(partialProduct);
3.1.3 id, name, price만 포함
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
// Pick 유틸리티 타입을 사용하여 특정 필드만 선택
type BasicProduct = Pick<Product, 'id' | 'name' | 'price'>;
// 사용 예시
const basicProduct: BasicProduct = {
id: 1,
name: "마우스",
price: 30000
};
console.log(basicProduct);
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
}
// Pick 유틸리티 타입을 사용하여 특정 필드만 선택
type BasicProduct = Pick<Product, 'id' | 'name' | 'price'>;
// 사용 예시
const basicProduct: BasicProduct = {
id: 1,
name: "마우스",
price: 30000
};
console.log(basicProduct);
3.2 맵드 타입 구현하기
3.2.1 Readonly 구현
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
}
// 모든 속성을 읽기 전용으로 만드는 맵드 타입
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
// 사용 예시
type ReadonlyProduct = MyReadonly<Product>;
const readonlyProduct: ReadonlyProduct = {
id: 1,
name: "태블릿",
price: 500000,
description: "10인치 태블릿",
category: "전자기기"
};
// 아래 코드는 에러 발생
// readonlyProduct.price = 400000; // Error: Cannot assign to 'price' because it is a read-only property.
console.log(readonlyProduct);
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
}
// 모든 속성을 읽기 전용으로 만드는 맵드 타입
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
// 사용 예시
type ReadonlyProduct = MyReadonly<Product>;
const readonlyProduct: ReadonlyProduct = {
id: 1,
name: "태블릿",
price: 500000,
description: "10인치 태블릿",
category: "전자기기"
};
// 아래 코드는 에러 발생
// readonlyProduct.price = 400000; // Error: Cannot assign to 'price' because it is a read-only property.
console.log(readonlyProduct);
3.2.2 Required 구현
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
}
// 모든 속성을 필수로 만드는 맵드 타입
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};
// 사용 예시
type RequiredProduct = MyRequired<Product>;
// 모든 필드가 필수이므로 아래처럼 모든 속성을 포함해야 함
const requiredProduct: RequiredProduct = {
id: 1,
name: "스마트폰",
price: 1200000,
description: "최신형 스마트폰", // 선택적 필드였지만 이제는 필수
category: "전자기기" // 선택적 필드였지만 이제는 필수
};
console.log(requiredProduct);
interface Product {
id: number;
name: string;
price: number;
description?: string;
category?: string;
}
// 모든 속성을 필수로 만드는 맵드 타입
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};
// 사용 예시
type RequiredProduct = MyRequired<Product>;
// 모든 필드가 필수이므로 아래처럼 모든 속성을 포함해야 함
const requiredProduct: RequiredProduct = {
id: 1,
name: "스마트폰",
price: 1200000,
description: "최신형 스마트폰", // 선택적 필드였지만 이제는 필수
category: "전자기기" // 선택적 필드였지만 이제는 필수
};
console.log(requiredProduct);