개요
TypeScript에서는 type
과 interface
두 가지 방식으로 타입을 정의할 수 있습니다.
둘의 차이점은 무엇일까요? 어떤 상황에서 어떤 것을 사용해야 할까요?
이 글에서는 type
과 interface
의 사용법과 차이점 뿐만 아니라,
무엇이 권장되는지, 또 IDE에서는 어떤 점이 다른 지 등 재밌는 내용을 다뤄보겠습니다.
표현법과 특징
type
type은 다양한 형태의 타입 정의를 가능하게 하는 TypeScript의 기능입니다.
그냥 모든 타입을 정의할 수 있습니다.
type은 단순히 객체의 구조를 정의할 뿐만 아니라, 유니언 타입, 교차 타입, 함수 타입, 배열 타입 등
다양한 형태의 타입을 정의하는 데 사용할 수 있습니다.
- 특징
- 유니언 타입(
|
)과 교차 타입(&
)을 사용할 수 있습니다. - 타입 별칭
type alias
을 사용할 수 있습니다. - 복잡한 타입(고급 타입 조합, 매핃 타입 등)을 정의할 수 있습니다.
interface
로 정의할 수 있는 모든 타입은type
으로도 정의할 수 있습니다.
- 유니언 타입(
interface
interface는 객체의 구조를 정의하는 데 특화된 기능입니다. 반대로 얘기하면 모든 경우에 type 을 대체할 수 없다는 거죠. 객체가 어떤 프로퍼티를 가져야 하고, 해당 프로퍼티들이 어떤 타입을 가져야 하는지 명확히 설명할 수 있습니다.
interface는 클래스와 상호작용하거나, 구현하는 구조를 정의하는 데 자주 사용됩니다.
- 특징
- 상속(
extends
)를 통해 확장이 가능합니다. - 클래스를 통해 구현될 수 있고, 클래스가 특정 구조를 따르도록 강제할 수 있습니다.
- 동일한 이름의 인터페이스가 여러 번 선언되면, TypeScript는 자동으로 병합합니다.(
선언 병합
) type
에 비해 더 제한적입니다.
- 상속(
type의 기능
interface
에서는 안 되는, type
만의 기능에 대해 알아보겠습니다. 거기에 interface
를 type
과 함께 쓰는 방법도 같이 알아볼까요?
1. 유니언 타입 (Union Types)
interface
로는 위와 같이 유니언 타입
을 정의할 수 없습니다.
interface
는 객체의 형태를 정의하는 데 특화되어 있기 때문에, 이러한 타입 조합은 불가능합니다.
2. 튜플 타입 (Tuple Types)
interface는 배열의 구조를 정의할 수 있지만, 특정 길이와 특정 인덱스 타입을 가진 튜플은 정의할 수 없습니다.
3. 교차 타입 (Intersection Types)
interface는 여러 인터페이스를 상속할 수 있지만, 이와 같은 직접적인 교차 타입은 사용할 수 없습니다.
4. 매핑된 타입 (Mapped Types)
매핑된 타입이란, TypeScript에서 기존의 객체 타입을 기반으로, 그 속성들을 변환하거나 조작하여 새로운 타입을 만드는 방법을 의미합니다. 반복적인 타입 정의를 줄이고, 코드의 유연성을 높일 수 있습니다.
직접적인 매핑은 아니지만, type
으로 매핑된 타입을 정의한 뒤 interface
에서 이를 사용할 수는 있습니다.
5. 조건부 타입 (Conditional Types)
위와 마찬가지로 type
으로 조건부 타입을 정의한 다음, interface
에서 이를 사용할 수는 있습니다.
6. 정확한 리터럴 타입 조합
예상 하셨듯이, 이 것도 type과 interface를 함께 쓰면서 사용할 수 있습니다.
type과 interface의 best practice
그러면 어떤 상황에서 type
을 사용하고, 어떤 상황에서 interface
를 사용해야 할까요?
ChatGPT 4o
chatGPT는 이에 대한 가이드라인을 제시합니다.
type
을 사용하는 경우- 유니언 타입, 교차 타입 등
interface
에서는 불가한 타입 정의가 필요할 때 - 유틸리니 타입, 조건부 타입 등을 사용할 때
- 함수 타입 정의할 때 (특히, 함수의 반환 타입이 복잡할 때
interface
보다 더 직관적이고 간단함)
- 유니언 타입, 교차 타입 등
interface
를 사용하는 경우- 객체 구조를 정의할 때
- 확장 가능성이 있을 때 (라이브러리 설계, 다른 개발자가 타입을 확장해야하는 경우 등)
TypeScript team은 무엇을 권장할까?
2024년 기준으로 TypeScript 팀이 권장하는 사항은 다음과 같습니다:
가능한 경우 interface
를 사용할 것: interface
는 기본적으로 객체 구조를 정의하고 확장 가능하며,
여러 선언을 자동으로 병합할 수 있어 유연성과 확장성이 좋습니다.
객체의 형태를 정의하는 경우에는 interface
를 선호하는 것이 좋습니다.
특정한 경우에만 type
사용: 유니언 타입, 교차 타입, 혹은 함수나 복잡한 타입 조합이 필요한 경우에는
type을 사용하는 것이 좋습니다.
또한, type을 통해 구현된 타입은 interface보
다 유연하게 작동하므로, 타입 조작이 필요한 경우에 유리합니다.
속설 : type보다 interface가 더 빠르다?
잠정 결론 : 눈에 띄는 성능 차이는 없다.
이 얘기를 시작하기 전에 먼저 '빠르다' 는 것이 무엇을 의미하는지 알아야 합니다.
여기서 type
과 interface
의 '빠름'은 코드 런타임 성능이 아니라, IDE 내 TypeScript type checker
의 효율성(속도) 과 관련 있습니다.
"interface
가 더 빠르다"는 사람들의 근거는 다음과 같습니다.
interface
는 확장 가능성과 선언 병합 기능 덕분에 타입 검사기의 처리 속도에 약간의 이점을 가질 수 있습니다.- 반면에,
type
은 유니언 타입, 교차 타입, 조건부 타입 등 복잡한 타입 정의에 사용될 수 있으며, 이런 복잡한 타입 조합은 IDE의 타입 검사기에 더 많은 부담을 줄 수 있습니다.
이런 이유로 TypeScript Performance Wiki
(Performance)
에서는 interface
가 type
보다 빠르다는 글을 올렸던 때가 있었습니다(만, 현재는 해당내용이 없어졌습니다.)
하지만 이러한 성능 차이는 대부분의 경우 매우 미미하며, 실제 개발에서 눈에 띄는 차이를 느끼기 어렵습니다.
- 실제로 1000개의
type
과 1000개의interface
를 비교한 개발자 Anastasios Theodosiou는 두 개의 차이가 확실하지 않다고 얘기했습니다. (Types vs Interface) - 유튜버 Matt Pocock 역시 수 천개의 type과 interface를 비교했지만 별 차이 없다고 밝혔고요. (TypeScript: Should you use Types or Interfaces?)
이런 속설이 자꾸 퍼져나가고 사람들이 궁금해하자, TypeScript Team은 많은 논의를 하게 됐습니다. 그리고 결국
type과 interface 중 어떤 것을 쓰던, 그 것이 성능에 영향을 미치는 중요한 요소는 아니다.
라는 결론을 잠정 도출합니다. 여러분은 어떻게 생각하시나요?
IDE에서의 차이점 (VScode)
IDE에서 type
과 interface
를 사용할 때 느낄 수 있는 차이점들도 있습니다.
이를 통해 팀원들과 어떤 typescript 컨벤션을 쓸 지 논의하시면 될 것 같습니다.
1. 타입 미리보기 (hovering)
타입 위에 마우스를 올리면, 대부분의 IDE는 해당 타입의 구조를 전체적으로 미리 보여줍니다. vscode에서도 그렇죠. 예를 들어 다음과 같이 동일한 객체 타입을 가지는 UserType과 UserInterface를 봅시다.
-
UserType
에 마우스를 올리면, 해당 타입의 구조를 미리 볼 수 있습니다. -
UserInterface
에 마우스를 올리면, 그냥interface UserInterface
라고만 나옵니다.
2. 타입 병합 (교차 타입 혹은 확장)
타입 추적은 IDE가 코드를 분석하여 변수의 타입을 추적하는 기능입니다.
vscode에서는 type
과 interface
의 타입 추적이 다르게 동작합니다.
-
AdminUserType
에 마우스를 올리면,AdminType
과UserType
이 병합된 것을 볼 수 있습니다. -
AdminUserInterface
에 마우스를 올리면,AdminUserInterface
만 나옵니다. (병합된 타입을 볼 수 없음)
3. 타입 병합 심화
위 예시처럼 두 개 정도의 타입을 병합할 때는 type
과 interface
의 차이가 크지 않습니다.
하지만 외부 라이브러리의 타입을 확장하거나, 여러 개의 ts 파일 내에서 확장을 했을 때 오류 추적은 어떨까요?
여기, interface 확장을 3번 정도 거쳐 만들어진 Manager라는 인터페이스가 있습니다. 그런데 한 가지 오류가 있군요. 뭔가 role에 대한 문제가 있어 보이죠? 하지만 error 메세지만 봐서는 잘 모르겠습니다. 일단 ChatGPT 형한테 물어봅니다.
이런… 제네릭<T>
이나 Omit<T,K>
유틸리티를 쓰라고 하는데… 뭔지 잘 모르겠습니다. 그냥 추적해볼까 싶습니다. 물론 subordinates에 들어가는 객체에 role을 넣으면 되지만 그건 의도한 동작은 아닙니다.
다시 타입을 살펴 봅시다.
거슬러 올라가 살펴보니 Person
에 대한 선언 병합할 때 role
이 껴 들어갔군요?
물론 Manager에는 role
속성이 필요합니다만, Employee
에는 필요 없습니다. 일단 지워봅시다.
그럼 다시 에러가 뜹니다.
아! 마참내..! 찾았습니다. Manager
타입에 있어야 할 role이 Person
에 있어서 에러가 났군요.
그럼 최종 코드는 다음과 같습니다.
type
으로 교차한다면 좀 더 쉬울지도 모릅니다.
같은 이름의 타입을 사용할 수 없으니(선언 병합 불가), 애초부터 헷갈릴 일은 없고, 해당 error 메세지에 충실하게 Employee 형식에 누락된 department
값만 넣으면 해결됩니다.
마치며
우아한 형제들에서는 type
을 사용합니다.
- 함수의 type을 정의할 때,
type
이 더 직관적이고 간단하다고 판단 - IDE에서 미리보기를 더 잘 지원함
- 원치 않는 선언 병합을 막음
한편 interface
는 다음과 같은 이유로 사용합니다.
- 외부 라이브러리를 사용할 때 선언 병합이 훨씬 편하다.
- 특히 대규모 프로젝트에서 여러 모듈에 동일한 인터페이스를 추가로 정의하는 경우가 많은데, 이때
interface
가 더 편리하다.
여러분의 팀에서는 어떤 컨벤션을 사용하시나요?