Notice
Recent Posts
Recent Comments
Link
관리 메뉴

윤일무이

[TypeScript] 타입 조작 본문

❔ TypeScript/💭 한 입 크기 TypeScript

[TypeScript] 타입 조작

썸머몽 2023. 12. 22. 16:28
728x90

타입 조작

  • 기본 타입이나 별칭, 인터페이스로 만든 원래 존재하던 타입들을 상황에 따라 유동적으로 다른 타입으로 변환시키는 기능
  • 제네릭을 비롯해 4가지 타입 조작 기능(인덱스드 엑세스 타입, keyof 연산자, Mapped 타입, 템플릿 리터럴 타입)에 대해 알아보자.

인덱스드 엑세스 타입

  • 객체, 배열, 튜플 타입으로부터 특정 프로퍼티나 특정 요소의 타입만 추출한다.
type Post = {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
};

const post: Post = {
  title: '제목',
  content: '본문',
  author: {
    id: 1,
    name: 'summermong',
    age: 27,
  },
};
  • Post 타입이 있고 이를 타입으로 하는 변수 post가 있다고 할 때, post의 author만 뽑아내고 싶다고 하자.
function printAuthorInfo(author: { id: number; name: string; age: number }) {
  console.log(`${author.name}-${author.id}`);
}

printAuthorInfo(post.author);
  • printAuthorInfo라는 함수를 만들었고 여기에 post의 author 객체를 매개변수로 전달하려고 한다.
  • 이 author 안에 들어 있는 프로퍼티들의 타입을 다 지정해주면 함수를 호출할 수 있다.
  • 그런데 Post 타입의 author에 무언가 수정되거나 추가되는 경우 관련된 코드를 모두 고쳐줘야 하는 번거로움이 있다.
  • 변수 post의 author를 통째로 꺼내서 쓰고 싶을 때, 아래와 같이 특정 타입만 가져오도록 인덱스드 엑세스 타입을 사용한다.
// 객체의 배열 타입
type PostList = {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
    age: number;
  };
}[];

// ['author']['id']처럼 이중으로 뽑아올 수 있음!
function printAuthorInfo(author: PostList[number]['author']) {
  console.log(`${author.name}-${author.id}`);
}

const post: PostList[number] = {
  title: '제목',
  content: '본문',
  author: {
    id: 1,
    name: 'summermong',
    age: 27,
  },
};

printAuthorInfo(post.author);

 

  • PostList 타입의 특정 프로퍼티만 가져올 수 있고, 따라서 새로운 프로퍼티가 추가/삭제/변경 되어도 수정할 필요가 없다.
  • 이처럼 인덱스를 이용해 특정 타입에 접근하기 때문에 인덱스드 엑세스 타입이라고 불린다.
  • 주의할 점은 저기에 넣는 것은 '값'이 아니라 타입이다!
  • [number]는 값이 아니라 타입임을 잊지 말자.
  • PostList[number]['author']['id']처럼 []를 연속 사용할 수 있다. 

튜플 타입에서는

type Tup = [number, string, boolean];
type Tup0 = Tup[0];
type Tup1 = Tup[1];
type Tup2 = Tup[2];
type TupNum = Tup[number];
  • Tup[number]는 튜플 타입의 최적의 공통 타입을 가져오기 때문에, 이 경우에는 3개의 유니온 타입을 가져온다.

keyof 연산자

  • 객체 타입으로부터 프로퍼티의 모든 key들을 string literal union 타입으로 추출하는 연산자
interface Person {
  name: string;
  age: number;
}

function getPropertyKey(person: Person, key: "name" | "age") {
  return person[key];
}

const person: Person = {
  name: "summermong",
  age: 27,
};
  • Person 객체 타입을 정의하고, 해당 타입을 갖는 변수 person을 선언한다.
  • getPropertyKey 함수를 만든다.
  • 이 함수는 두 개의 매개변수가 있고, 두 번째 매개변수 key에 해당하는 프로퍼티의 값을 첫 번째 매개변수 person에서 꺼내 반환한다.
  • 이 때 key의 타입을 "name" | "age"로 정의했는데, 이러면 Person 타입의 프로퍼티가 추가/수정 될 때마다 바꿔야 한다.
interface Person {
  name: string;
  age: number;
  location: string; // 추가
}

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

const person: Person = {
  name: "summermong",
  age: 27,
};
  • keyof 연산자는 [keyof 타입] 형태로 사용하며 타입의 모든 프로퍼티 key를 string literal union 타입으로 추출한다.
  • 그래서 keyof Person은 name | age | location이 된다.
  • 단, keyof 연산자의 뒤에는 무조건 타입만 와야 한다. (값, 변수에 사용하려고 하면 오류 발생)

with typeof 연산자

  • typeof 연산자는 특정 값의 타입을 문자열로 반환하는 연산자다.
  • 타입을 정의할 때 사용하면 특정 변수의 타입을 추론하는 기능을 갖는다!
type Person = typeof person;

// keyof 연산자는 뒤에 타입이 와야 한다.
function getPropertyKey(person: Person, key: keyof typeof person) {
  return person[key];
}

const person = {
  name: 'summermong',
  age: 27,
};

getPropertyKey(person, 'name');
  • 즉 변수 person의 타입을 추론해 Person 타입이 된다.

맵드 타입

  • 기존 객체 타입을 기반으로 새로운 객체 타입을 만드는 타입 조작 기능
interface User {
  id: number;
  name: string;
  age: number;
}

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

function updateUser(user: User) {
  // 유저 정보 수정 기능이 있다고 칩시다
}

updateUser({ // ❌
  name: 'wintermong';
});
  • 유저 정보를 관리하는 프로그램을 만들기 위해 User 객체 타입을 정의한다.
  • 유저 한 명의 정보를 불러 오는 함수 fetchUser를 정의했고, 유저 한 명의 정보를 수정하는 함수 updateUser를 정의 했다.
  • 이 때 updateUser에는 수정한 내용만 전달하고 싶은데, updateUser의 매개변수 user의 타입이 User이기 때문에 수정하지 않은 프로퍼티들도 전달해야 하는 번거로움이 생긴다.
interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = {
  id?: number;
  name?: string;
  age?: number;
}

(...)

function updateUser(user: PartialUser) {
  // ... 유저 정보 수정 기능
}

updateUser({ // ✅
  name: 'wintermong'
});
  • 이 때 User와 거의 유사하지만 선택적 프로퍼티만 담고 있는 PartialUser라는 타입을 새로 만들어줬다.
  • 그리고 updateUser의 매개변수 user의 타입을 이것으로 바꿔주면 선택적 프로퍼티기 때문에 수정한 것만 전달해도 된다.
  • 하지만 User와 PartialUser는 불필요하게 중복되는 코드가 많다.
  • 이를 개선하기 위해 맵드 타입을 이용한다.
interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = {
  [key in "id" | "name" | "age"]?: User[key];
};

(...)
  • [key in 'id' | 'name' | 'age']는 해당 객체 타입의 key가 한번은 id, 한번은 name, 한번은 age가 된다는 뜻이다.
  • 따라서 3개의 프로퍼티를 갖는 객체 타입으로 정의된다.
    • key가 “id” 일 때 → id : User[id] → id : number
    • key가 “name”일 때 → name : User[user] → name : string
    • key가 “age”일 때 → age : User[age] → age : number
  • [key in 'id' | 'name' | 'age']? 라고 하면 모든 프로퍼티가 선택적 프로퍼티가 된다.
interface User {
  id: number;
  name: string;
  age: number;
}

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

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

(...)
  • 이 때 keyof 연산자를 쓰면 User 객체의 프로퍼티를 순회한다.
  • 또 모든 프로퍼티가 읽기 전용 프로퍼티가 된 타입으로도 만들 수 있다!

템플릿 리터럴 타입

  • string 리터럴 타입을 기반으로 특정 패턴을 갖는 문자열 타입을 만드는 기능
type Color = 'red' | 'black' | 'green';
type Animal = 'dog' | 'cat' | 'chicken';

type ColoredAnimal = `${Color}-${Animal}`;

const coloredAnimal: ColoredAnimal = 'black-cat'
  • Color 타입과 Animal 타입은 각각 3개의 string literal 타입으로 이뤄진 union 타입이다.
  • ColoredAnimal 타입은 이 둘을 조합해 만들 수 있는 모든 가지수의 string literal 타입으로 이루어진 union 타입이다.
  • 만약 Color, Animal 타입의 값이 바뀌면 ColoredAnimal 타입에 추가해야 하는 것들이 점점 많아지고, 수정도 번거롭게 된다.
  • 이 때 템플릿 리터럴 타입을 쓰면 알아서 조합해준다!

 


 

 

**출처: 한 입 크기로 잘라먹는 타입스크립트 (인프런, 이정환 강사님)

728x90