TypeScript/한 입 크기로 잘라먹는 타입스크립트

[타입스크립트 기본] 타입 별칭과 인덱스 시그니처

해갈 2023. 7. 6. 10:56

해당 게시글은

강의 사이트, 인프런에서 이정환님이 진행하시는

'한 입 크기로 잘라먹는 타입스크립트' 

들어보며 블로그를 작성하려고 합니다.

다음 게시글 내용은 해당 강의에 핸드북의 내용으로

출처는 다음과 같습니다.

 

https://ts.winterlood.com/7250edd7-a3fd-4662-b756-f11f927c73f2

 

타입스크립트를 소개합니다 - 타입스크립트 개론

한 입 크기로 잘라먹는 타입스크립트

ts.winterlood.com

 

이번 글에서는

타입 정의를 마치 변수처럼 하게 해주는

"타입 별칭" 이라는 문법과 함께

이전 시간에 배운 객체 타입을 좀 더 유연하게 정의하도록 도와주는

"인덱스 시그니처" 라는 문법도 배워보았습니다.

이번 실습은 chapter4.ts 에서 진행합니다.

 

타입 별칭(Type Alias)

타입 별칭을 이용하면,

다음과 같이 변수를 선언하듯 타입을 정의할 수 있습니다.

// 타입 별칭
type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};
...

'type 타입_이름 = 타입' 형태로 정의합니다.

위 코드는 타입 이름으로는 User,

타입으로는 여러 개의 프로퍼티가 있는 객체 타입을 정의했습니다.

 

이렇게 만든 타입 별칭은 다음과 같이

변수의 타입을 정의할 때 타입 주석과 함께 이용할 수 있습니다.

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};

let user: User = {
  id: 1,
  name: "이정환",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

let user2: User = {
  id: 2,
  name: "홍길동",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

 

참고로 동일한 스코프에 동일한 이름의 타입 별칭을 선언하는 것은 불가능합니다.

마치 변수 선언과 유사합니다. 

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};

type User = {} //불가능

그러나 스코프가 다르다면,

다음과 같이 중복된 이름으로 여러 개의 별칭을 선언해도 상관없습니다.

 

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};

function test() {
  type User = string;
}

test 함수 내부에서는 User 가 string 타입이 되고,

test 함수 바깥에서는 User 가 객체 차입이 됩니다.

 

이전 시간에 타입 관련 문법은 컴파일과 함께 모두 사라진다고 살펴보았습니다.

타입 별칭 또한 타입 관련 문법이기 때문에

컴파일 결과 사라집니다.

다음은 chapter4.ts 를 tsc 로 컴파일 한 결과입니다.

let user = {
    id: 1,
    name: "이정환",
    nickname: "winterlood",
    birth: "1997.01.07",
    bio: "안녕하세요",
    location: "부천시",
};
let user2 = {
    id: 2,
    name: "홍길동",
    nickname: "winterlood",
    birth: "1997.01.07",
    bio: "안녕하세요",
    location: "부천시",
};
export {};

인덱스 시그니처(Index Signature)

인덱스 시그니처는

객체 타입을 유연하게 정의할 수 있도록 돕는 특수한 문법입니다.

다양한 국가들의 영어 코드를 저장하는 객체가 하나 있다고 가정합니다.

type CountryCodes = {
  Korea: string;
  UnitedState: string;
  UnitedKingdom: string;
};

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

만약 이때,

countryCodes 에 100개의 프로퍼티(국가 코드)가 추가되어야 한다면,

타입 정의에도 각 프로퍼티를 모두 정의해주어야 하기 때문에 매우 불편할 겁니다.

type CountryCodes = {
  Korea: string;
  UnitedState: string;
  UnitedKingdom: string;
  // (... 약 100개의 국가)
  Brazil : string
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

 

바로 이럴 때, 인덱스 시그니처를 이용하면

다음과 같이 간단하게 타입을 정의할 수 있습니다.

type CountryCodes = {
  [key: string]: string;
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

[key : string] : string 은 인덱스 시그니처 문법으로

이 객체 타입에는 "key 가 string 타입이고,

value 가 string 타입인 모든 프로퍼티를 포함된다" 라는 의미입니다.

따라서 Korea: "ko" 나 Brazil: "bz" 처럼

key 와 value 가 모두 string 타입인 이런 프로퍼티들이

굳이 일일히 타입들을 직접 명시하지 않아도

타입에 자동으로 포함됩니다.

 

만약 국가 코드를 숫자로 보관하는 객체가 하나 더 필요하다고 하면,

그때의 타입은 다음과 같이 정의하면 됩니다.

type CountryNumberCodes = {
  [key: string]: number;
};

 

또 이때 반드시 포함해야 하는 프로퍼티가 있다면

다음과 같이 직접 명시해도 됩니다.

type CountryNumberCodes = {
  [key: string]: number;
  Korea: number;
};

 

한 가지 주의할 점은

인덱스 시그니처를 사용하면서 동시에 추가적인 프로퍼티를 또 정의할 때에는

인덱스 시그니처의 value 타입과 직접 추가한 프로퍼티의 value 타입이

호환되거나 일치해야 합니다.

따라서 다음과 같이 서로 호환되지 않는 타입으로 설정하면 

오류가 발생합니다.

type CountryNumberCodes = {
  [key: string]: number;
  Korea: string; // 오류!
};

string 타입은 number 타입과 호환되지 않기 때문입니다.

호환에 대해서는 3섹션에서 자세히 다룹니다.