WeniVooks

검색

타입스크립트 베이스캠프

고급 타입

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;
}
  1. id 제외하는 interface를 만들어주세요.
  2. 모든 필드가 선택적이 되는 interface를 만들어주세요.
  3. 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;
}
  1. 모든 속성을 읽기 전용으로 만드는 Readonly<T> 구현
  2. 모든 속성을 필수로 만드는 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);
3.1 제네릭3.3 타입 가드