Menu

TypeScript interface vs type 차이: 언제 무엇을 쓸까

interface와 type은 둘 다 객체 모양을 기술할 수 있고, 대부분의 경우 어느 쪽이든 동작합니다. 실제 차이인 선언 병합, 유니언과 매핑된 타입, extends와 교차 타입, 암묵적 인덱스 시그니처, 오류 보고와 컴파일러 성능, 그리고 명확한 선택 기준을 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

객체의 모양을 기술하는 데는 interface와 type이 같은 일을 하고, 모양이 맞으면 한쪽의 값을 다른 쪽에 대입할 수 있습니다. 차이는 가장자리에 있습니다. type은 인터페이스가 이름 붙일 수 없는 것(유니언, 튜플, 계산된 타입)에 이름을 붙일 수 있고, 인터페이스는 타입 별칭이 할 수 없는 몇 가지(병합, 검사되는 extends)를 할 수 있습니다.

TypeScript는 객체 타입을 구조로 비교하므로, 대입 가능성에는 선언의 이름이 중요하지 않습니다. 이어지는 내용은 선택이 실제로 중요해지는 곳의 목록입니다.

비교표

기능interfacetype
객체 모양예예
선택적 속성, readonly, 메서드, 인덱스 시그니처예예
제네릭예예
유니언(A | B)아니요예
튜플, 원시 타입, 함수 타입 단독아니요(함수 타입은 호출 시그니처로만)예
매핑된 타입과 조건부 타입아니요예
확장extends, 충돌은 오류&, 충돌은 never가 됨
선언 병합예아니요(중복 식별자)
Record<string, T>에 대입아니요예, 속성이 맞으면
클래스의 implements예예, 객체 타입이라면
재귀 정의예예

type만 할 수 있는 것

객체 모양 하나가 아닌 것은 모두 타입 별칭이 필요합니다.

이 중 어느 것도 interface로는 쓸 수 없습니다(마지막 두 가지는 매핑된 타입과 조건부 타입에서 다룹니다). 객체 모양에 무엇을 쓰든 모든 코드베이스가 결국 어딘가에서 type을 쓰게 되는 실질적인 이유입니다.

interface만 할 수 있는 것: 선언 병합

같은 스코프에서 같은 이름의 interface 선언 두 개는 하나로 합쳐집니다. 같은 이름의 type 선언 두 개는 오류 TS2300, Duplicate identifier입니다.

interface Settings {
  theme: string;
}
interface Settings {
  fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both

type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.

병합은 라이브러리 타입을 밖에서 확장하는 방법입니다. 전역 Window, Express의 Request, 라이브러리의 테마 타입에 속성을 추가하는 식입니다. 사용자가 보강해야 할 수도 있는 타입을 배포한다면 인터페이스를 쓰세요. 자신의 애플리케이션 코드에서는 뜻하지 않은 병합(스크립트 파일 두 개가 같은 이름의 전역 인터페이스를 선언하는 경우)이, 둘이 같은 속성을 다른 타입으로 선언하지 않는 한 조용히 일어납니다. 일부 팀이 type을 선호하는 이유 중 하나입니다.

extends와 교차 타입

인터페이스는 extends로, 타입 별칭은 &로 확장합니다. 대부분 같은 결과를 내지만, 충돌하는 속성을 다루는 방식이 다릅니다. extends는 선언에서 충돌을 보고합니다.

index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
  Types of property 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

교차 타입은 같은 충돌을 조용히 받아들이고 그 속성을 never(number이기도 한 string)로 만듭니다. 오류는 나중에 값을 만들려고 할 때에야 나타납니다.

오류는 원인이 된 선언에서 멀리 떨어진 객체를 가리킵니다. 다른 객체 타입으로 객체 타입을 만들 때는 extends가 더 나은 메시지를 줍니다.

인덱스 시그니처: 놓치기 쉬운 차이

객체 타입의 타입 별칭은 암묵적 인덱스 시그니처를 받으므로 Record<string, unknown>이 기대되는 곳에 넘길 수 있습니다. 인터페이스는 그렇지 않습니다. 의도된 설계입니다. TypeScript 팀의 설명(TypeScript 저장소의 이슈 #15300)에 따르면 인터페이스는 나중의 선언으로 보강될 수 있으므로 인덱스 시그니처를 추론하는 것이 덜 안전하고, 지금 규칙을 바꾸면 너무 많은 코드가 깨집니다.

@ts-expect-error로 표시한 호출도 그대로 실행되어 Alan의 필드를 출력합니다. 컴파일 시점의 규칙이기 때문입니다. Record<string, ...>으로 타입이 지정된 로깅, 직렬화, 쿼리 헬퍼에 인터페이스 값을 넘길 때 헷갈리는 오류가 나는 흔한 이유입니다. 그 선언 하나를 type으로 바꾸거나, 값을 펼치거나, 헬퍼의 매개변수를 인터페이스나 제네릭으로 지정하세요.

성능과 오류 메시지

TypeScript 위키의 Performance 페이지("Preferring Interfaces Over Intersections" 절)는 객체 타입을 조합할 때 type Foo = Bar & Baz & { ... }보다 interface Foo extends Bar, Baz { ... }를 권합니다. 이유는 이렇습니다. 인터페이스는 속성 충돌을 감지하는 하나의 평평한 객체 타입이고, 인터페이스 사이의 타입 관계는 캐시되며(교차 타입 전체는 캐시되지 않습니다), 교차 타입에 대해 값을 검사하면 평평해진 타입보다 먼저 모든 구성 요소를 검사합니다. 조합된 타입이 많은 대규모 코드베이스에서는 차이가 나지만, 평범한 객체 모양 몇 개로는 측정되지 않을 것입니다.

같은 페이지는 인터페이스가 더 잘 표시된다고도 말합니다. 인터페이스는 마우스를 올렸을 때와 오류 메시지에서 이름으로 표시되지만, 교차 타입 별칭은 종종 구성 요소로 펼쳐서 출력되어 긴 메시지를 읽기 어렵게 만듭니다.

무엇을 쓸까

잘 통하는 규칙:

  1. 객체 모양: interface. 검사되는 extends, 큰 조합에서의 더 명확한 오류를 주고, 라이브러리 사용자가 보강할 수 있게 합니다. TypeScript 핸드북의 경험칙 "use interface until you need to use features from type"(type의 기능이 필요할 때까지는 interface를 쓰라)과 일치합니다.
  2. 그 밖의 모든 것: type. 유니언, 튜플, 함수 타입, 리터럴 타입, 그리고 매핑된 타입, 조건부 타입, 템플릿 리터럴 타입으로 만든 모든 것.
  3. 예외: Record<string, ...> 매개변수에 맞아야 하는 객체 모양이거나 일부러 병합을 막고 싶을 때는 type을 쓰세요.

모든 것에 type을 쓰는 것도 일관된 선택이고, 많은 코드베이스가 그렇게 합니다. 피해야 할 유일한 선택은 둘을 무작위로 섞는 것입니다. 읽는 사람이 차이가 의도된 것인지 궁금해하게 되기 때문입니다.

자주 묻는 질문

TypeScript에서 type과 interface의 차이는 무엇인가요?

둘 다 객체 모양을 기술하며 그 용도로는 바꿔 쓸 수 있습니다. type은 유니언, 튜플, 원시 타입, 매핑된 타입이나 조건부 타입에도 이름을 붙일 수 있지만 interface는 할 수 없습니다. interface는 선언 병합과 속성 충돌을 검사하는 extends를 지원합니다. 또한 type으로 쓴 객체 타입은 Record<string, unknown> 같은 인덱스 시그니처 타입에 대입할 수 있지만, 인터페이스는 할 수 없습니다.

type과 interface 중 무엇을 써야 하나요?

TypeScript 핸드북의 경험칙은 type에만 있는 기능이 필요해질 때까지 interface를 쓰라는 것입니다. 실제로는 객체 모양에는 인터페이스를, 유니언, 튜플, 함수 타입, 계산된 타입에는 type을 쓴다는 뜻입니다. 모든 것에 type을 쓰는 팀도 문제없이 지냅니다. 중요한 것은 일관된 규칙 하나입니다.

TypeScript에서 interface가 type보다 빠른가요?

객체 타입을 조합할 때는 경우에 따라 그렇습니다. TypeScript 팀의 성능 가이드는 큰 교차 타입(A & B & { ... })보다 interface ... extends를 권합니다. 인터페이스 사이의 관계는 캐시되고 인터페이스는 하나의 평평한 타입이기 때문입니다. 단순한 객체 모양이라면 의미 있는 차이가 없습니다.

클래스가 타입 별칭을 구현할 수 있나요?

네, 별칭이 객체 타입(또는 객체 타입의 교차)이라면 가능합니다. class Point implements PointType { ... }는 동작합니다. 클래스는 유니언 타입을 구현할 수 없으며, 그것은 오류 TS2422입니다.

인터페이스가 타입 별칭을 확장할 수 있나요?

네, 별칭이 객체 타입이라면 가능합니다. type Base = { id: string }; interface User extends Base { name: string }는 올바릅니다. 유니언 별칭은 확장할 수 없습니다. 반대 방향으로는 타입 별칭이 &로 인터페이스 위에 쌓을 수 있습니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기