topbg
Typescript

타입 조작하기

2023.06.15

1. 타입 조작이란?

  • 원래 있던 타입들을 상황이나 조건에 따라 유동적으로 다른 타입으로 변경하는 타입스크립트의 특수한 기능
  • 제네릭도 타입을 조작할 수 있는 기능중에 하나이다.

2. 인덱스드 엑세스 타입(Indexed access type)

  • 인덱스를 이용하여 다른 타입내에 특정 프로퍼티 타입을 추출하는 타입

객체 프로퍼티 타입 추출

interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
  };
}

const post: Post = {
  title: "post title",
  content: "main",
  author: {
    id: 1,
    name: "user1",
  },
};
  • author의 프로퍼티를 출력하는 함수
function printAuthorInfo(author: { name: string; id: number;}) {
  console.log(`${author.name}-${author.id}`);
}

만일 author의 프로퍼티를 새로 추가한다면 author 타입도 추가해야 하므로 불편하다.

interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number // new property
  };
}

function printAuthorInfo(author: { name: string; id: number; age: number}) {}

이러한 번거로움을 해결하기 위해 indexed access type을 이용하면 된다.

interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number // new property
  };
}

function printAuthorInfo(author: Post["author"]) {
  console.log(`${author.name}-${author.id}`);
  // author 타입은 {id:number; name:string; age:number}
}
  • Post["author] Post타입으로부터 author 프로퍼티 타입을 추출
  • author에 새로운 프로퍼티를 선언해도 Post의 author 프로퍼티로부터 타입을 자동적으로 추론한다.

주의

  • 객체에서 indexed aceess type을 사용할때 대괄호에 변수나 해당하지 않는 프로퍼티를 사용하면 에러가 발생한다.
interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
}

const authorKey = "author";

function printAuthorInfo(author: Post[authorKey]) { //
  console.log(`${author.id} - ${author.name}`);
}

function printAuthorInfo(author: Post["what"]) { //
  console.log(`${author.id} - ${author.name}`);
}

중첩 사용

interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
}


function printAuthorInfo(author: Post["author"]["id"]) {
  console.log(`${author.id} - ${author.name}`);
}
// author의 매개변수 타입은 객체 타입에서 number 타입으로 변경 된다.

배열 요소에서 타입 추출

type Postlist = {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
}[];

배열의 한 요소의 객체 author의 타입을 추출

function printAuthorInfo(author: Postlist[number]["author"]) {
  console.log(`${author.id} - ${author.name}`);
} 

배열 한 요소의 타입 추출

const post: Postlist[number] = {
  title: "post title",
  content: "main",
  author: {
    id: 1,
    name: "user1",
    age: 18
  },
};  //

const post: Postlist[0] = {
  title: "post title",
  content: "main",
  author: {
    id: 1,
    name: "user1",
    age: 18,
  },
}; //

index에 들어가는 것은 타입만 올 수 있고 나머지는 올 수 없음

const num = 0;

const postlist3: Postlist[num] = {
  title: "post title",
  content: "main",
  author: {
    id: 1,
    name: "user1",
    age: 18,
  },
}; //

튜플 요소에서 타입 추출

튜플의 index literal number 타입 추출

type Tup = [number, string, boolean];

type Tup0 = Tup[0]; // Tup0은 number type으로 추출
type Tup1 = Tup[1]; // Tup1은 string type으로 추출
type Tup2 = Tup[2]; // Tup2은 boolean type으로 추출
type Tup3 = Tup[3]; // ❌ Tup[3]의 요소가 없음

튜플의 index number 타입 추출

type Tup = [number, string, boolean];
type TupNum = Tup[number]; // string | number | boolean의 유니언타입으로 최적의 공통 타입 추출

3. keyof & typeof 연산자

객체의 프로퍼티의 모든 key들을 string literal union형태로 추출하는 연산자

interface Person {
  name: string;
  age: number;
  locaton: string; // new property
}

아래 코드와 같이 선언하면 새로운 프로퍼티를 추가혀면, 함수 매개변수의 key의 타입도 추가해야한다.

function getPropertyKey(person: Person, key: "name" | "age" | "location") { // location 추가됨
  return person[key];
}

keyof으로 선언하기

function getPropertyKey(person: Person, key: keyof Person) {
  // keyof Person의 결과값은 "name"|"age"|"location"
  return person[key];
}

const person: Person = {
  name: "user",
  age: 18,
  location: "seoul"
};

getPropertyKey(person, "name");

keyof 연산자는 오직 타입(type alias, interface, 일반타입)만 올수 있고 변수나 값이 올수 없다.

function getPropertyKey(person: Person, key: keyof person) { // ❌ person의 객체타입의 변수가 대입
  return person[key];
}

const person: Person = {
  name: "user",
  age: 18,
  location: "seoul"
};

typeof와 keyof 사용하기

type Person = typeof person;

function getPropertyKey(person: Person, key: keyof Person) {
  return person[key];
}

const person: Person = {
  name: "user",
  age: 18,
};

typeof 연산자를 이용하여 keyof 사용하기

function getPropertyKey(person: Person, key: keyof typeof person) {
  return person[key];
}

const person = {
  name: "user",
  age: 18,
};

타입 추론 순서

typeof person

{
  name:string;
  age:number;
}

keyof typeof person

"name" | "age"

keyof가 적용된 이 블로그 커밋

keyof 사용한 해당 커밋

4. 맵드 타입(Mapped Types)

[key in "property key의 유니언타입"] : property의 값의 타입

  • 기존의 객체 타입을 기반으로 새로운 객체 타입을 만드는 타입 조작 기능입니다.
  • 맵드 타입은 type alias만 선언 가능하다.
interface User {
  id: number;
  name: string;
  age: number;
}

function fetchUser(): User {
  return { id: 1, name: "user", age: 18 };
}

한명의 유저 정보를 수정할때

interface User {
  id: number;
  name: string;
  age: number;
}

// 중복 interface
interface PartialUser{
  id?: number;
  name?: string;
  age?: number;
}


function updateUser(user: User) {} //
function updateUser(user: PartialUser) {} //

updateUser({ age: 20 }); // 변경되는 값만 보내고 싶을때

맵드 타입을 이용하면 중복 interface를 선언하지 않아도 된다.

interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUsers = {
  [key in "id" | "name" | "age"]?: User[key]; // 선택적 property
};

function updateUser(user: PartialUser) {} //

updateUser({ age: 20 });
  • property 키가 무엇인지 정의, key가 "id", "name", "age"
  • property키들이 어떤 value타입을 가질 것인지 정의, User["id"] | User["name"] | User["age"]
  • {id : User["id"]; name : User["name"]; age : User["age"]}

만일 프로퍼티의 갯수가 많아지면 keyof 연산자를 사용하면 된다.

interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = {
   [key in keyof User]?: User[key];
};


// 제네릭 선언 방식
type PartialUser<T> = {
  [key in keyof T]?: T[key];
};
function updateUser(user: PartialUser<User>) {}

반환값이 readonly일 경우

interface User {
  id: number;
  name: string;
  age: number;
}

type ReadonlyUser = {
  readonly [key in keyof User]: User[key];
};

function readonlyUser(): ReadonlyUser {
  return { id: 1, name: "user", age: 18 };
}
const user = readonlyUser();
user.id = 1; // ❌ 읽기 전용이라 수정할 수 없다.

5. 템플릿 리터럴 타입

문자열 리터럴 유형을 기반으로 하며 특정 패턴의 문자열 리터럴 타입으로 확장할 수 있습니다.

type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColoredAnimal = "red-dog" | "red-cat" | "red-chicken" | "black-dog" | "..........";

Color타입에 red가 변경되면 ColoredAnimal의 red-dog, red-cat의 red를 하나하나 변경해야 하는 불편함이 발생한다.

백틱을 이용하여 리터럴 타입을 선언하면 된다.

type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColorAnimal = `${Color}-${Animal}`;
// type ColorAnimal = "red-dog" | "red-cat" | "red-chicken" | "black-dog" | "black-cat" | "black-chicken" | "green-dog" | "green-cat" | "green-chicken"
const coloredAnimal: ColorAnimal = "red-cat";

referance

이해한 것을 정리하다보니
잘못된 부분이 있을 수도 있습니다.
댓글로 잘못된 부분을 알려주세요.