Dandy Now!
  • [TypeScript] Utility Type 완전 분석
    2025년 06월 18일 21시 39분 30초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    TypeScript Utility Type 완전 분석

    TypeScript는 JavaScript에 정적 타입을 부여하여 코드의 안정성과 생산성을 향상시키는 언어이다. TypeScript의 강력한 기능 중 하나는 바로 유틸리티 타입이다. 유틸리티 타입은 기존 타입을 기반으로 새로운 타입을 쉽게 생성할 수 있도록 돕는 도구 모음이다. 이 글에서는 TypeScript의 주요 유틸리티 타입들을 상세히 알아본다.

    1. 유틸리티 타입이란 무엇인가?

    유틸리티 타입은 TypeScript의 내장된 제네릭 타입 함수라고 할 수 있다. 이들은 기존 타입의 속성을 변형하거나 선택적으로 추출하는 등의 작업을 통해 새로운 타입을 생성한다. 이를 통해 개발자는 반복적인 타입 정의를 줄이고, 코드의 가독성과 재사용성을 높일 수 있다.

    • 코드 재사용성 증대: 이미 정의된 타입을 활용하여 새로운 타입을 만들 수 있다.
    • 타입 안정성 강화: 특정 상황에 맞는 엄격한 타입 검사를 가능하게 한다.
    • 생산성 향상: 복잡한 타입 정의를 간결하게 표현할 수 있다.

    2. 주요 유틸리티 타입 종류

    Partial<T>

    Partial<T>은 타입 T의 모든 프로퍼티를 선택적(optional)으로 만든다. 즉, 해당 타입의 모든 속성에 ?를 붙인 것과 같은 효과를 낸다.

    • 사용 예시: 객체의 일부만 업데이트할 때 유용하다.
    interface User {
      id: number;
      name: string;
      email: string;
    }
    
    type PartialUser = Partial<User>;
    // 결과: { id?: number; name?: string; email?: string; }
    
    const userUpdate: PartialUser = {
      name: "홍길동",
    };

    Readonly<T>

    Readonly<T>는 타입 T의 모든 프로퍼티를 읽기 전용(readonly)으로 만든다. 이는 해당 프로퍼티에 값을 할당하는 것을 방지한다.

    • 사용 예시: 객체의 불변성을 보장해야 할 때 사용한다.
    interface Product {
      id: number;
      name: string;
      price: number;
    }
    
    type ReadonlyProduct = Readonly<Product>;
    // 결과: { readonly id: number; readonly name: string; readonly price: number; }
    
    const product: ReadonlyProduct = {
      id: 1,
      name: "책상",
      price: 50000,
    };
    
    // product.price = 60000; // 에러: 'price' 속성을 읽기 전용으로 설정했기 때문에 할당할 수 없다.

    Pick<T, K>

    Pick<T, K>는 타입 T에서 프로퍼티 K를 선택하여 새로운 타입을 생성한다. KT의 프로퍼티 키들의 유니온 타입이어야 한다.

    • 사용 예시: 특정 인터페이스에서 필요한 속성만 추출하여 새로운 타입을 만들 때 편리하다.
    interface User {
      id: number;
      name: string;
      email: string;
      address: string;
    }
    
    type UserProfile = Pick<User, "name" | "email">;
    // 결과: { name: string; email: string; }
    
    const userProfile: UserProfile = {
      name: "김철수",
      email: "kim@example.com",
    };

    Omit<T, K>

    Omit<T, K>는 타입 T에서 프로퍼티 K를 제외한 새로운 타입을 생성한다. KT의 프로퍼티 키들의 유니온 타입이어야 한다. Pick과 정반대의 개념이다.

    • 사용 예시: 기존 타입에서 특정 속성만 제외하고 싶을 때 유용하다.
    interface Product {
      id: number;
      name: string;
      price: number;
      description: string;
    }
    
    type ProductWithoutDescription = Omit<Product, "description">;
    // 결과: { id: number; name: string; price: number; }
    
    const simpleProduct: ProductWithoutDescription = {
      id: 101,
      name: "노트북",
      price: 1200000,
    };

    Exclude<T, U>

    Exclude<T, U>는 타입 T에서 U에 할당 가능한 모든 유니온 멤버를 제외하여 새로운 타입을 생성한다. 유니온 타입에서 특정 타입을 제거할 때 사용한다.

    • 사용 예시: 유니온 타입에서 특정 값을 제외한 새로운 유니온 타입을 만들 때 활용한다.
    type AllColors = "red" | "green" | "blue" | "yellow";
    type PrimaryColors = Exclude<AllColors, "yellow" | "green">;
    // 결과: "red" | "blue"
    
    const color: PrimaryColors = "red";
    // const anotherColor: PrimaryColors = "yellow"; // 에러: '"yellow"' 형식은 'PrimaryColors' 형식에 할당할 수 없다.

    Extract<T, U>

    Extract<T, U>는 타입 T에서 U에 할당 가능한 모든 유니온 멤버를 추출하여 새로운 타입을 생성한다. Exclude와 정반대의 개념이다.

    • 사용 예시: 유니온 타입에서 특정 값을 추출하여 새로운 유니온 타입을 만들 때 사용한다.
    type AllShapes = "circle" | "square" | "triangle" | 1 | 2;
    type GeometricShapes = Extract<AllShapes, "circle" | "square" | "triangle">;
    // 결과: "circle" | "square" | "triangle"
    
    const shape: GeometricShapes = "square";
    // const numberShape: GeometricShapes = 1; // 에러: '1' 형식은 'GeometricShapes' 형식에 할당할 수 없다.

    NonNullable<T>

    NonNullable<T>는 타입 T에서 nullundefined를 제외한 새로운 타입을 생성한다.

    • 사용 예시: null 또는 undefined가 될 수 있는 타입에서 이들을 제거하여 안전하게 타입을 다룰 때 유용하다.
    type MaybeString = string | null | undefined;
    type SureString = NonNullable<MaybeString>;
    // 결과: string
    
    const text: SureString = "Hello";
    // const nullText: SureString = null; // 에러: 'null' 형식은 'string' 형식에 할당할 수 없다.

    Parameters<T>

    Parameters<T>는 함수 타입 T의 매개변수 타입을 튜플 형태로 추출하여 새로운 타입을 생성한다.

    • 사용 예시: 함수의 매개변수 타입을 동적으로 얻어와 다른 함수에서 활용할 때 유용하다.
    function greet(name: string, age: number) {
      console.log(`안녕하세요, ${name}님! ${age}살이시군요.`);
    }
    
    type GreetParams = Parameters<typeof greet>;
    // 결과: [name: string, age: number]
    
    const params: GreetParams = ["영희", 25];
    // const invalidParams: GreetParams = ["철수"]; // 에러: 'age' 인수가 필요하다.

    ReturnType<T>

    ReturnType<T>는 함수 타입 T의 반환 타입(return type)을 추출하여 새로운 타입을 생성한다.

    • 사용 예시: 함수의 반환 타입을 기반으로 다른 타입 정의를 할 때 편리하다.
    function calculateSum(a: number, b: number): number {
      return a + b;
    }
    
    type SumResult = ReturnType<typeof calculateSum>;
    // 결과: number
    
    const result: SumResult = calculateSum(10, 20);

    Required<T>

    Required<T>는 타입 T의 모든 프로퍼티를 필수(required)로 만든다. Partial과 반대되는 개념이다.

    • 사용 예시: 선택적 프로퍼티가 포함된 타입에서 모든 프로퍼티를 필수로 만들어야 할 때 유용하다.
    interface Config {
      theme?: string;
      fontSize?: number;
      darkMode?: boolean;
    }
    
    type FullConfig = Required<Config>;
    // 결과: { theme: string; fontSize: number; darkMode: boolean; }
    
    const myConfig: FullConfig = {
      theme: "dark",
      fontSize: 16,
      darkMode: true,
    };
    
    // const partialConfig: FullConfig = { theme: "light" }; // 에러: 'fontSize' 및 'darkMode' 속성이 없다.

    Record<K, T>

    Record<K, T>는 키 K와 값 T를 가지는 객체 타입을 구성한다. 여기서 K는 유효한 프로퍼티 키(string, number, symbol 또는 이들의 리터럴 유니온)를 나타내고, T는 해당 키에 할당될 값의 타입을 나타낸다.

    • 사용 예시: 특정 키와 값의 패턴을 가진 객체 타입을 정의할 때 유용하다.
    type PagePaths = "home" | "about" | "contact";
    type PageInfo = { title: string; url: string };
    
    type Pages = Record<PagePaths, PageInfo>;
    /* 결과:
    {
      home: { title: string; url: string; };
      about: { title: string; url: string; };
      contact: { title: string; url: string; };
    }
    */
    
    const sitePages: Pages = {
      home: { title: "메인 페이지", url: "/" },
      about: { title: "회사 소개", url: "/about" },
      contact: { title: "문의하기", url: "/contact" },
    };
    
    // const invalidPages: Pages = { home: { title: "메인 페이지", url: "/" } }; // 에러: 'about' 및 'contact' 속성이 없다.

    3. 유틸리티 타입의 활용

    유틸리티 타입은 실제 개발 환경에서 다양한 방식으로 활용될 수 있다.

    • API 응답 데이터 처리: API에서 받아온 데이터의 일부만 필요하거나, 특정 필드를 선택적으로 만들 때 Pick, Partial 등을 사용할 수 있다.
    • 상태 관리: 애플리케이션의 상태를 정의할 때, 특정 상태 값만 업데이트해야 할 경우 Partial을 활용하여 유연하게 처리할 수 있다.
    • 불변 객체 생성: 객체의 불변성을 강제해야 하는 상황에서 Readonly를 사용하여 오류를 방지할 수 있다.
    • 함수 시그니처 추출: 고차 함수를 작성하거나, 함수의 매개변수와 반환 타입을 동적으로 다루어야 할 때 ParametersReturnType이 큰 도움이 된다.

    TypeScript의 유틸리티 타입은 단순한 문법적 설탕을 넘어, 타입 시스템을 훨씬 강력하고 유연하게 만들어주는 핵심 기능이다. 이들을 적절히 활용하면 더욱 견고하고 유지보수하기 쉬운 코드를 작성할 수 있다.

    728x90
    반응형
    댓글