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

조아영·2024년 9월 25일

제네릭(Generic)

함수에서 파라미터를 사용하듯이, 타입을 변수처럼 사용하는 방식입니다. 기본적인 문법은 <> 안에 제네릭으로 받을 타입의 이름을 넣어주시면 됩니다. 보통 T, U 등의 이름을 자주 사용합니다.

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>를 써주지 않아도 타입 추론 가능

useState에서 사용하기

import { useState } from "react";

function App() {
  const [counter, setCounter] = useState<number>(1);
  // const [counter, setCounter] = useState(1); // ✅ 이렇게 작성해도 타입 추론 가능
  const increment = () => {
    setCounter((prev) => prev++);
  };
  return <div onClick={increment}>{counter}</div>;
}

export default App;

제네릭 타입추론

TypeScript는 제네릭 타입도 자동으로 추론할 수 있습니다.

function identity<T>(arg: T): T {
    return arg;
}

let output = identity("Hello"); // ✅ T는 string으로 추론

◼ 유틸리티 타입(Utility Type)

유틸리티 타입은 타입 변환을 용이하게 하기 위해 TypeScript에서 제공하는 타입들입니다. 데이터를 처리할 때 유용한 함수들을 Utility Function이라고 부르는 것처럼, 유틸리티 타입은 타입을 다루는 데 도움을 줍니다.

Pick<T, K>

T 타입에서 K의 집합을 선택해 새로운 타입을 구성합니다.

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

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

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

Omit<T, K>

T 타입에서 K를 제외한 타입을 구성합니다.

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

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

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

Exclude<T, U>

T 타입에서 U 타입에 속하지 않는 타입만을 추출합니다.

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

Partial<T>

T 타입의 모든 프로퍼티를 선택적으로 만듭니다. 전체가 아닌 일부의 값을 사용하기 위해서 사용됩니다.

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

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

const todo1 = {
    title: 'organize desk',
    description: 'clear clutter',
};

const todo2 = updateTodo(todo1, {
    description: 'throw out trash',
});

console.log(todo1); // { title: 'organize desk', description: 'clear clutter' }
console.log(todo2); // { title: 'organize desk', description: 'throw out trash' }

ReadOnly<T>

T 타입의 모든 프로퍼티를 읽기 전용으로 만듭니다. 즉, 생성된 타입의 프로퍼티는 재할당할 수 없습니다.

interface Todo {
    title: string;
}

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

todo.title = 'Hello'; // 오류: 읽기 전용 프로퍼티에 재할당할 수 없음

Record<K, T>

K를 키로 하고 T를 값으로 가지는 타입을 구성합니다. 이 유틸리티는 타입의 프로퍼티들을 다른 타입에 매핑시키는 데 사용될 수 있습니다.

interface PageInfo {
    title: string;
}

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

const x: Record<Page, PageInfo> = {
    about: { title: 'about' },
    contact: { title: 'contact' },
    home: { title: 'home' },
};

Extract<T, U>

T 타입에서 U 타입에 할당할 수 있는 모든 타입을 추출합니다.

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

ReturnType<T>

T 함수 타입의 반환 타입을 추론합니다.

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

type User = ReturnType<typeof getUser>;

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

Parameters<T>

T 함수의 매개변수 타입을 튜플 형태로 구성합니다.

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

type LogParams = Parameters<typeof log>;

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

log(...params); // 1: Hello, world!

Awaited<T>

비동기 함수의 반환 타입을 추론할 때 사용합니다.

async function fetchData(): Promise<string> {
    return "Hello, world!";
}

// fetchData 함수의 반환 타입 추론
type FetchDataType = Awaited<ReturnType<typeof fetchData>>;

const data: FetchDataType = await fetchData();
console.log(data); // "Hello, world!"

◼ 응용

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

// 기초가 되는 Todo 타입
type Todo = {
    id: number;
    title: string;
    completed: boolean;
}

// Pick과 Omit 타입을 이용하여, 각 기능에 맞는 타입 생성
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'>;

// Partial 타입도 함께 활용
type EditTodo = Partial<Todo> & TodoId;

비동기 함수에서 반환 타입을 추론하기

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

0개의 댓글