본문 바로가기
프론트엔드/TS 공부

타입 별칭(type alias) / 인덱스 시그니처(index signature)

by 학습하는 청년 2023. 7. 26.

최종 수정 : 2024-05-27
 

cf. 리터럴 타입

변수의 값을 타입으로 정의하는 것이며, let을 사용하느냐 const를 사용하느냐에 따라 타입이 다르게 추론된다.

// let은 타입추론, const는 리터럴 타입
let literal1 = '타입스크립트 공부';
const literal2 = '타입스크립 익히기';

let medium = 90;
const large = 100;

타입 별칭(type alias)

타입에 '이름'을 정하는 문법이다. 복잡한 타입에 이름을 붙이고 재사용하고 싶을 때 사용한다.
- 특정 타입이나 인터페이스 등을 참조할 수 있는 타입 변수를 의미
- 가장 큰 장점 : 반복되는 타입 코드를 줄여 준다.
 
타입 별칭과 인터페이스의 차이
1) 코드 에디터에서 표기 방식 차이
- 타입 별칭은 타입 정보가 나타난다.
 
2) 사용할 수 있는 타입의 차이
- 인터페이스 : 주로 객체의 타입을 정의
- 타입 별칭 : 일반 타입에 이름을 짓는 데 사용하거나 유니언 타입, 인터섹션 타입 등에도 사용 / 제네릭이나 유틸리티 타입 등에서도 사용할 수 있다.

type BookTitle = string;
type BookInfo = Book | Page; // 유니언 타입
type BookInfo = Book & Page; // 인터섹션 타입

인터페이스와 타입 별칭의 정의를 함께 사용할 수도 있다.

interface BookInfo {
 name: string;
 page: number;
}

type BookPublish {
 state: boolean;
}

type BookState = BookInfo & BookPublish;

 
3) 타입 확장 관점에서 차이
타입 확장 : 이미 정의되어 있는 타입들을 조합하여 더 큰 의미의 타입을 만드는 것
- 인터페이스 : 상속
- 타입 별칭 : 인터섹션
 
=> 유연하게 타입을 확장하는 관점에서는 타입 별칭보다 인터페이스가 더 유리하다.


인덱스 시그니처(index signature)

- 정확히 속성 이름을 명시하지 않고 속성 이름의 타입과 속성 값의 타입을 정의하는 문법
- 단순히 객체와 배열을 인덱싱할 때 활용될 뿐만 아니라 객체의 속성 타입을 유연하게 정의할 때도 사용된다.
- 속성 이름과 속성 값이 정의된 것에 부합한 정보면 1개든 n개든 모두 추가할 수 있다는 장점이 있다.

interface BookList {
 [name: string]: string;
}

// ex)
type CountryCodes = {
  [Key: string]: string;
};

let countryCodes: CounteryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
};

 
인덱스 시그니처가 적용되어 있는 경우에는 구체적으로 어떤 속성이 제공될지 알 수 없어 코드 자동 완성이 되지 않는다.

interface Book {
 [property: string]: string
 athor: string; // 반드시 있어야 한다.
 page: number; // 반드시 있어야 한다.
}

let typescript: Book = {
 athor: 'teadyeong',
 page: 769
 subtitle: 'extend js', // 인덱스 시그니처에 의한 추가
 publisher: 'A' // 인덱스 시그니처에 의한 추가
}

객체의 속성 이름과 속성 값이 정해져 있는 경우
-> 속성 이름과 속성 값 타입을 명시해서 정의하고
 
속성 이름은 모르지만 속성 이름의 타입과 값의 타입을 아는 경우
-> 인덱스 시그니처를 활용


Enum과 타입 별칭

// Enum을 사용 (권장)
enum UserType {
 Admin = 'admin',
 User = 'user',
 Guest = 'guest',
}

const role = UserType.Admin;
console.log(role === UserType.Guest); // false

// 타입 별칭과 Union을 사용
type UserType = 'admin' | 'user' | 'guest'

const role: UserType = 'admin';
console.log(role === 'guest');

 
둘의 차이는 JavaScript로 트랜스파일링 했을 때, 드러난다.
1) Enum은 별도의 자바스크립트 객체를 생성하고, 그 객체를 사용한다.

"use strict";
var UserType;
(function (UserType) {
  UserType["Admin"] = "admin";
  UserType["User"] = "user";
  UserType["Guest"] = "guest";
})(UserType || (UserType = {}));
const role = UserType.Admin;
console.log(role === UserType.Guest);

 
2) 반면 타입 별칭은 타입스크립트에서만 의미 있는 코드이므로, 트랜스파일 했을 때 추가로 객체를 생성하지 않고 값만 사용하는 코드가 만들어진다.

"use strict";
const role = 'admin';
console.log(ole === 'guest');

 
대부분의 경우 Enum 또는 타입 별칭을 모두 사용할 수 있으나, Enum의 목적에 맞는 경우라면 Enum 문법을 사용하는 것이 좋다.


선언 병합(declaration merging)
- 인터페이스는 동일한 이름으로 인터페이스를 선언하면 내용을 합치는 특성이 있다.

interface Book {
 name: string;
 page: number;
}

interface Book {
 athor: string;
}

// =>
interface Book {
 name: string;
 page: number;
 athor: string;
}

 
 
타입 별칭은 언제 쓰는 것이 좋은가?
1) 타입 별칭으로만 정의할 수 있는 타입들
- 주요 데이터 / 유니언 / 인터섹션 / 제네릭 / 유틸리티 / 맵드
- 제네릭은 인터페이스와 타입 별칭에 모두 사용할 수 있다.
- 유틸리티 타입이나 맵드 타입은 기존에 정의된 타입을 변경하거나 일부만 활용할 때 사용한다.

type MyBook = string; // 주요 데이터 타입
type BookInfo = string | number; // 유니언 타입
type BookInfo = Book & BookPage; // 인터섹션 타입

// 제네릭
type BookInfo<T> = {
 name: string;
 title: T;
}

// 유틸리티 타입
type Book = { name: string; page: number; author: string; }
type BookSelected = Pick<Book, 'name'>

// 맵드 타입
type Selector<T, K extends keyof T> = {
 [P in K]: T[P];
}

 
2) 백엔드와의 인터페이스 정의
영역 간 접점(데이터)을 서로 맞추는 작업
인터페이스로도 가능하다. 타입 확장 측면에서, 타입 별칭이 제공하는 미리보기 효과보다 인터페이스로 작업하는 게 더 이점이 크다.

// 1. 타입 별칭으로 API 함수의 응답 형태를 정의
type Book = {
 name: string;
 page: number;
}

function fetchData(): Book {
 return axios.get('http://localhost:3000/books/1');
}

// 2. 인터페이스로 API 함수의 응답 형태를 정의
interface Book {
 name: string;
 page: number;
}

function fetchData(): Book {
 return axios.get('http://localhost:3000/books/1');
}

댓글