[TypeScript] 제네릭과 유틸리티 타입

제네릭

타입을 클래스나 함수 등에서 파라미터처럼 사용할 수 있음

function printAnything<T>(arr: T[]): void {
  for(let i = 0; i < arr.length; i ++) {
    console.log(arr[i]);
  }
}

printAnything(['a', 'b', 'c']);  // <string>을 쓰지 않아도 타입 추론 가능
printAnything([1, 2, 3]);  // <number>를 쓰지 않아도 타입 추론 가능

 

T, U는 제네릭에서 많이 사용되는 단어이고 꼭 저런 단어가 아니더라도 내가 식별할 수 있는 단어면 활용할 수 있다!

 

hook에서 활용하기

function App() {
  const [counter, setCounter] = useState<number>();
  const increment = () => {
    setCounter(prev => prev++);
  };
  
  return <div onClick={increment}>{counter}</div>;
}

- 위 경우에서 useState에 제네릭을 사용해주지 않으면 state의 타입은 undefined로 추론되어, 다른 값을 대입하고자할 때 에러가 생김

위와 같이 제네릭을 사용하면 number | undefined로 활용할 수 있음

 

 

유틸리티 타입

데이터를 이용해 간단한 계산을 하는 함수들을 Utility Function이라고 부르는 것처럼 타입을 통해 간단한 계산을 수행해주는 타입을 유틸리티 타입이라고 한다.

즉, 타입 변환을 위해 타입스크립트에서 지원하는 문법

 

Pick<T, K>

특정 타입에서 필요로하는 요소만 골라서 사용할 수 있다.

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>;

const todo: TodoPreview = {
  title: 'clean room',
  completed: false,
};

- Todo에서 title, completed 두가지 키만 pick 해서 TodoPreview라는 타입을 만들었다.

 

Omit<T, K>

원하는 타입에서 어떤 것만 제외할지 지정할 수 있다.

여러 타입을 빼고싶은 경우 | 연산자를 사용해 제외할 타입들을 지정할 수 있다.

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Omit<Todo, 'description' | 'completed'>;

const todo: TodoPreview = {
  title: 'clean room',
};

 

Pick과 Omit은 유사한 결과를 낼 수 있지만, 어떤 걸 사용할건지 딱 지정해주는 것이기 때문에 인지적으로 Pick을 사용하는 것이 좋을 수 있다. 하지만 지정해줘야하는 요소의 수가 많아진다면 적은 편을 선택하는 것이 나을수도!

 

Exclude<T, U>

앞의 원본 타입(T)에서 뒤에 지정한 타입(U)와 겹치는 것을 제거한다.

type T0 = Exclude<"a"|"b"|"c", "a">;  // "b" | "c"
type T1 = Exclude<string| number | (() => void), Function>;  // string | number

 

Partial<T>

어떤 타입을 Partial로 감싸주면 그 타입 내의 모든 조건들이 optional로 변한다.

interface PartialTodo {
  title?: string;
  description?: string;
}

function updateTodo(todo: Todo, filedsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

 

Readonly<T>

읽기전용 객체를 만들 수 있다.

interface Todo {
  title: string;
}

const todo: Readonly<Todo> = {
  title: 'delete inactive users',
}

todo.title = 'eat delicious pizza';  // 에러, 읽기 전용 프로퍼티에 재할당하려 함

 

Record<K, T>

객체에서 key의 타입과 value의 타입을 각각 지정하고 싶은 경우

 

interface PageInfo {
  title: string;
}

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

// key는 Page 타입, value는 PageInfo 타입이 되어야 한다고 지정
const x: Record<Page, PageInfo> = {
  about: { title: 'about' },
  contact: { title: 'contact'},
  home: { title: 'home' },
};

// key에는 Page의 세 값 중 하나, value는 PageInfo 타입인 { title: ~~ }의 형태로 고정됨

 

Extract<T, U>

원본 타입(T)에서 지정한 타입(U)와 중복되는 값만 추출한다.

type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;  // a만 남는다
type T1 = Extract<string | number | (() => void), Function>;  // () => void 타입만 남는다

 

ReturnType<T>

함수의 반환 값을 타입으로 사용할 수 있게 함

function getUser() {
  return { name: 'alice', age: 25 };
}

// getUser의 반환값을 User 타입으로 활용할 수 있다
type User = ReturnType<typeof getUser>;

const user: User = { name: 'alice', age: 25 };

 

Parameters<T>

함수의 매개변수 타입을 추론하기위해 사용할 수 있다.

function log(message: string, userId: number): void {
  console.log(`${userId}: ${message}`);
}

typeof LogParams = Parameters<typeof log>;

const params: LogParams = ["Hello, world!", 1];

log(...params);

 

Awaited<T>

async 함수는 promise를 반환하는데, promise의 반환값의 타입을 추론할 수 있다.

// string 타입의 데이터를 받아오는 비동기 함수
async function fetchData(): Promise<string> {
  return 'hello world';
}

// fetchData의 반환타입인 Promise에서 실행 완료 후 반환될 데이터의 타입을 추론할 수 있음
type FetchDataType = Awaited<ReturnType<typeof fetchData>>;

const data: FetchDataType = await fetchData();
console.log(data);  // hello world

 

 

유틸리티타입 활용 예시

하나의 base 타입으로 여러가지 타입 만들기

// base type
type Todo = {
  id: number;
  title: string;
  completed: boolean;
}

// base type 기반으로 기능에 맞는 타입 만들기
type TodoId = Pick<Todo, 'id'>;

type CreateTodo = Pick<Todo, 'title' | 'completed'>;
type CreateTodo = Omit<Todo, 'id'>;

type ToggleTodo = Pick<Todo, 'id' | 'completed'>;
type ToggleTodo = Omit<Todo, 'title'>;

type EditTodo = Partial<Todo> & TodoId;

 

함수를 기반으로 타입을 빼오기

라이브러리 등을 사용했을 때 함수의 반환값을 정확히 알지 못할 수 있다.

그럴 때 유틸리티타입을 활용해 반환값을 추론하면 더 빠르고 안전하게 개발을 진행할 수 있다.

async function exampleFunction(): Promise<number> {
  return new Promise((resolve) => {
    resolve(42);
  });
}

 

'TypeScript' 카테고리의 다른 글

[TypeScript] 타입 어노테이션과 추론  (0) 2024.09.24
[Typescript] 타입스크립트의 타입 선언  (1) 2024.09.24