Typescript

[Typescript] Utility Type이란?

__ellie 2021. 9. 16. 01:25
반응형

타입스크립트에서는 다른 일반적인 프로그래밍 언어에서는 찾을 수 없는, 타입을 변환하는 것이 가능하다.
별 모양의 타입을 별 모양의 일부분만 변환하는 transform도 가능하다.

utility 타입을 가져다가 쓸 수도 있지만, 정확하게 어떻게 이것이 가능한 것인지 먼저 알아본다.

Index Type

{
  const obj = {
    name: 'ellie'
  }
  obj.name;
  obj['name']
}

이것처럼 인덱스를 기준으로 타입을 결정할 수 있다.

type Animal = {
  name: string; 
  age: number;
  gender: 'male' | 'female'
}

type Name = Animal['name'] // string
const text: Name = 12 //error

type Gender = Animal['gender'] // male | female

type Keys = keyof Animal; // 'name' | 'age' | 'gender' 문자열 유니언이 들어간다.

const key: Keys = 'name';

type Person = {
  name: string; 
  gender: Animal['gender']
}

const person: Person = {
  name: 'ellie', 
  gender: 'male'
}

index 타입을 이용하면 다른 타입에 있는 키에 접근해서 그 키의 value의 타입을 그대로 다시 선언할 수 있다.

Mapped Type

기존에 있는 타입들을 이용하면서 조금 다른 형태로 변환하는 것

type Video = {
  title: string;
  author: string;
}

type VideoOptional = {
  title?: string; 
  author?: string;
}

type VideoReadonly = {
  readonly title: string; 
  readonly author: string;
}
[1,2].map(item => item * item)

type Optional<T> = {
  [P in keyof T]?: T[P] // for ...in과 동일하다. 
  // P키는 T 타입의 모든 키들 중 하나다. 
}

type VideoOptional = Optional<Video>

한 번 정의해놓으면 재사용성이 높다. generic과 비슷하다.

type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

const video = Readonly<Video> = {
  title: 'hello', 
  author: 'ellie'
}

기존 타입에서 다른 타입으로 성질을 변화할 수 있다.

type Nullable<T> = {
  [P in keyof T]: T[P] | null;
}

const video = Nullable<Video> = {
  title: null, 
  author: null
}
  type Proxy<T> = {
    get(): T;
    set(value: T): void;
  }

  type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>
  }

Proxify라는 타입은 전달되는 어떤 오브젝트를 빙글빙글 돌면서 타입을 Proxy라는 타입으로 한 단계 감싸는 역할을 한다.

Map 타입을 이용하면, 기존의 타입에서 다른 타입으로 변환해주는 것을 해줄 수 있다.

Conditional Type

조건이 맞으면 어떤 타입을 선택한다

type Check<T> = T extends string? boolen : number;
type Type = Check<string> // string은 string을 상속하기 때문에 boolean이 된다. 
type TypeName<T> = T extends string
? 'string'
: T extends number 
? 'number' 
: T extends boolean 
? 'boolean'
: T extends undefined 
? 'undefined'
: T extends Function 
? 'function' 
: 'object';

type T0 = TypeName<string>; // string
type T1 = TypeName<'a'>; // string
type T2 = TypeName<() => void> // function

conditional type은 어떤 타입이 이런 타입이라면, 이 타입을 써야지 라고 조건적으로 타입을 결정할 수 있는 타입이다.

Utility Type

ReadOnly

type Todo = {
  title: string; 
  description: string; 
}

function display(todo: Readonly<Todo>) {
  todo.title = 'java'; //error!
}
  • 이렇게 가변성, 수정이 가능한 오브젝틀르 여기저기 전달하는 것은 굉장히 위험하다.

  • 항상 불변성을 보장하는 것이 중요하다.

  • 이렇게 많이 쓰는 타입들은 유틸리티 타입이라고 해서

  • 이미 타입스크립트 개발자가 만들어 놓았다.

Partial Type

Partial 타입은 기존의 타입 중에서 부분적인 것만 타입을 허용하고 싶을 때 이용할 수 있다.

type Todo = {
  title: string; 
  description: string; 
  label: string; 
  priority: 'high' | 'low';
}

function updateTodo(todo: Todo, fieldToUpdate: Partial<Todo>): Todo {
  return {...todo, ...fieldToUpdate}
}

const todo: Todo = {
  title: 'hello', 
  description: 'learn typescript!', 
  label: 'study', 
  priority: 'high'
}

const updated = updateTodo(todo, {priority: 'low'})

Pick Type

type Video = {
  id: string; 
  title: string; 
  url: string; 
  data: string;
}

function getVideo(id: string): Video {
  return {
    id, 
    title: 'video', 
    url: 'https://...', 
    data: 'byte-data...'
  }
}

function getVideoMetadata(id: string): Pick<Video, 'id' | 'title'> {
  return {
    id: string
  }
}
  • 기존의 타입에서 원하는 속성만 골라서 타입을 만들 수 있다.
  • 재사용성을 높이려면...
type VideoMetadaga = Pick<Video, 'id' | 'title'>
  • 이런식으로 선언해서 사용할 수 있다.

Pick 구현

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}
  • 어떤 타입을 전달받아오고, K 라는 것은 T 타입에 있는 키들을 상속한 아이들이다.
  • 그래서 항상 Pick을 사용할 때 기존의 T에 있는 키를 써야지, 다른 키를 쓰면 에러가 발생한다.
  • 전달된 K들에 한해서만 빙글빙글 돌면서 타입을 결정한다.

Omit Type

Pick과는 반대로 원하는 것을 빼버릴 수 있다.

type videoMetadata = Omit<Video, 'url' | 'data'>; 
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Exclude<T, U>  = T extends U ? never : T;
  • any타입을 가지고 있는 key들을 가지고 오고, Pick을 이용하는데
  • T 타입을 그대로 유지하면서, T에 있는 key들 중에 K를 제외한 아이들을 Pick하게 된다.

Record type

type PageInfo = {
  title: string;
}

type Page = 'home' | 'about' | 'contact';

const nav: Record<Page, PageInfo> = {
  home: {
    title: 'Home',
  }, 
  aboute: {
    title: 'About'
  }
}
  • Map과 비슷하게 하나와 하나를 연결하고 싶을 때 하나를 키를 쓰고 나머지를 다른 타입으로 묶고 싶을 때 유용하게 쓸 수 있다.

기타

Readonly나 Nullable, Record를 많이 쓴다.

type Product = 'cat' | 'dog';
type NewProduct = Capitalize<Product> // 'Cat' | 'Dog'