본문 바로가기
Frontend/TypeScript

[TypeScript] TypeScript 타입 조작 기능 - 5

by 우킹쥐 2024. 4. 17.
728x90

 

     


    타입 조작이란?

    - 이미 할당 된 기본 타입, 별칭, 인터페이스 타입들을 새로운 타입으로 변환할 수 있는 타입스크립트 문법

     

    타입 조작 기능

    - 제너릭

    - 인덱스드 엑세스 타입

    - keyof 연산자

    - Mapped(맵드) 타입

    - 템플릿 리터럴 타입

    - 조건부 타입

     

    제너릭

    - https://sillimmouse.tistory.com/75

     

     

    인덱스드 엑세스 타입

    - 객체, 배열, 튜플에서 특정 프로퍼티 혹은 요소의 타입을 추출하는 타입

    - 대괄호 속에 들어가는 String Literal 타입인 “author” 를 인덱스라고 하며 인덱스를 이용해 특정 타입에 접근하다고 하여 인덱스드 엑세스 타입라고 한다.

    - 인덱스에는 값이 아니라 타입(기본타입, 리터럴 타입 등)만 가능하다.

    // 인덱스드 엑세스 타입
    // 1. 객체 추출
    interface Post {
    title: string;
    content: string;
    author: {
      id: number;
      name: string;
      // age: number; // 추가 시 post 변수, printAuthorInfo 함수 수정 필요
    };
    }
    
    const post: Post = {
      title: "게시글 제목",
      content: "게시글 본문",
      author: {
        id: 1,
        name: "이정환",
        // age: 50
      },
    };
    
    // function printAuthorInfo(author: { id: number; name: string }) {
    //   console.log(`${author.id} - ${author.name}`);
    // }
    function printAuthorInfo(author: Post["author"]) { // {id : number, name: string, age:number}
      console.log(`${author.id} - ${author.name}`);
    }
    
    // 2. 배열 추출
    type PostList = {
      title: string;
      content: string;
      author: {
        id: number;
        name: string;
        age: number;
      };
    }[];
    
    const post2: PostList[number] = { // 배열의 요소 타입을 추출할 때에는 인덱스에 number 타입을 넣어주면 된다 [0] 리터럴 변수도 가능
      title: "게시글 제목",
      content: "게시글 본문",
      author: {
        id: 1,
        name: "이정환",
        age: 50
      },
    };
    
    // 3. 튜플 타입
    type Tup = [number, string, boolean];
    
    type Tup0 = Tup[0]; // number
    type Tup1 = Tup[1]; // string
    type Tup3 = Tup[number] // number | string | boolean // number 타입을 넣으면 마치 튜플을 배열 처럼 인식해 배열 요소의 타입을 추출

     

    keyof 연산자

    - 특정 객체 타입으로 부터 프로퍼티 키들을 모두 스트링 리터렁 유니온 타입으로 추출하는 연산자

    - keyof 연산자: 객체 타입으로부터 프로퍼티의 모든 key들을 String Literal Union 타입으로 추출하는 연산자, 오직 타입에만 적용할 수 있는 연산자이다.

    - typeof 연산자: 자바스크립트에서 특정 값의 타입을 문자열로 반환하는 연산자로 타입 정의에 사용 시 타입추론의 기능을 할 수 있다.

    // keyof 연산자
    interface Person {
      name: string;
      age: number;
    }
    
    const person: Person = {
      name: "kim"
      age: 27,
    };
    
    // function getPropertyKey(person: Person, key: "name" | "age") {
    //   return person[key];
    // }
    function getPropertyKey(person: Person, key: keyof Person) {
      return person[key];
    }
    getPropertyKey(person, "name");
    
    
    // Typeof와 Keyof 함께 사용하기
    type Person2 = typeof person; 
    
    function getPropertyKey2(person: Person, key: keyof typeof person) {
      return person[key];
    }

     

    Mapped(맵드) 타입

    - 기존의 객체타입으로 부터 새로운 객체를 만드는 타입

    - 인터페이스로 만들수없고 type 만 가능하다.

    // 맵드 타입
    interface User {
      id: number;
      name: string;
      age: number;
    }
    
    // type PartialUser = { // 중복 프로퍼티를 작성
    //   id?: number;
    //   name?: string;
    //   age?: number;
    // }
    type PartialUser = {
      // [key in "id" | "name" | "age"]?: User[key];
      [key in keyof User]?: User[key];
      // readonly [key in keyof User]?: User[key];
    };
    
    function updateUser1(user: User) {
      console.log(user)
    }
    function updateUser2(user: PartialUser) {
      console.log(user)
    }
    
    // updateUser1({
    //   age: 25 // error - User의 id, name 없음
    // });
    updateUser2({
      age: 25
    });

     

    템플릿 리터럴 타입

    - 스트링 리터럴 타입을 기반으로 정해진 패턴의 문자열만 포함하는 타입

    - 특정 패턴을 갖는 String 타입을 만드는 기능

    // 템플릿 리터럴 타입
    type Color = "red" | "black" | "green";
    type Animal = "dog" | "cat" | "chicken";
    
    // type ColoredAnimal = `red-dog` | 'red-cat' | 'red-chicken' | 'black-dog';
    type ColoredAnimal = `${Color}-${Animal}`;

     

    조건부 타입

    - extend와 삼항 연산자를 이용해 조건에 따라 각각 다르게 정의하는 타입

    // 조건부 타입 소개
    // 1. 기본 타입
    type A = number extends string ? number : string;
    
    // 2. 객체 타입
    type ObjA = {
      a: number;
    };
    type ObjB = {
      a: number;
      b: number;
    };
    type B = ObjB extends ObjA ? number : string;
    
    // 3. 제네릭 조건부 타입
    type StringNumberSwitch<T> = T extends number ? string : number;
    
    let varA: StringNumberSwitch<number>; // string
    let varB: StringNumberSwitch<string>; // number
    
    // 유니온 타입
    // function removeSpaces(text: string | undefined | null) {
    //   if (typeof text === "string") {
    //     return text.replaceAll(" ", "");
    //   } else {
    //     return undefined;
    //   }
    // }
    
    // 조건부 타입
    // function removeSpaces<T>(text: T): T extends string ? string : undefined {
    //   if (typeof text === "string") {
    //     // return text.replaceAll(" ", ""); // error - 함수 내부에서는 조건부 타입 결과를 알지 못함
    //     return text.replaceAll(" ", "") as any;
    //   } else {
    //     // return undefined; // error
    //     return undefined as any;
    //   }
    // }
    
    // 조건부 타입 + 오버로드 시그니쳐 추가
    function removeSpaces<T>(text: T): T extends string ? string : undefined;
    function removeSpaces(text: any) {
      if (typeof text === "string") {
        return text.replaceAll(" ", "");
      } else {
        return undefined;
      }
    }
    
    let result = removeSpaces("hi im winterlood");
    result.toUpperCase();
    let result2 = removeSpaces(undefined);

     

     

    분산적인 조건부 타입

    - 조건부 타입에 유니온 타입을 사용 시 분산적인 조건부 타입으로 동작이 된다.

    - 즉, 각각의 타입이 유니온으로 묶인 결과가 타입으로 지정되게 된다.

    // 분산적인 조건부 타입
    type StringNumberSwitch<T> = T extends number ? string : number;
    
    let a: StringNumberSwitch<number>; // number
    let b: StringNumberSwitch<string>; // string
    let c: StringNumberSwitch<number | string>; // string | number
    let d: StringNumberSwitch<boolean | number | string>; // string | number
    
    // Exclude(제외하다) 조건부 타입 구현하기
    type Exclude<T, U> = T extends U ? never : T;
    
    type A = Exclude<number | string | boolean, string>; // number | boolean // never는 유니온타입 사용 시 타입에서 없어진다.
    
    type Extract<T, U> = T extends U ? T : never;
    
    type B = Extract<number | string | boolean, string>; // string
    
    // 조건부 타입을 분산적으로 사용하고 싶지 않은 경우
    // 타입에 [] 배열로 감싸주면 된다.
    type Extract2<T, U> =  [T] extends [U] ? T : never;
    
    type C = Extract2<number | string | boolean, string>; // never // 첫번째 변수만 결과로 조회 됨

     

     

    조건부 타입에서 타입 추론

    - infer : Inference의 약자로 추론이라는 뜻

    - 조건부 타입 작성 시 특정 타입을 지정해서 조건부를 작성하는 것이 아니라, 넘어오는 변수의 타입을 추론해서 조건부 타입결과를 조회하는 방법

    // 조건부 타입에서 타입 추론
    
    // infer
    // type ReturnType<T> = T extends () => string ? string : never;
    type ReturnType<T> = T extends () => infer R ? R : never;
    
    type FuncA = () => string;
    
    type FuncB = () => number;
    
    type A = ReturnType<FuncA>; // string
    type B = ReturnType<FuncB>; // number
    type C = ReturnType<number>; // never
    
    // Promise의 resolve 타입을 infer를 이용해 추출하는 예 
    type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;
    // 1. T는 프로미스 타입이어야 한다.
    // 2. 프로미스 타입의 결과값 타입을 반환해야 한다.
    
    type PromiseA = PromiseUnpack<Promise<number>>; // number
    
    type PromiseB = PromiseUnpack<Promise<string>>; // string

     

     

     

    출처

    • 한 입 크기로 잘라먹는 타입스크립트(TypeScript)
    728x90