본문 바로가기
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
반응형