
Types vs. interfaces in TypeScript라는 글의 해석 및 type only import/export를 추가한 글입니다.
타입스크립트에서 타입을 정의하는 데는 type과 interface라는 두 가지 옵션이 있습니다. TypeScript에 대해 가장 자주 묻는 질문 중 하나는 interface를 사용해야 하는지 type을 사용해야 하는지입니다.
이 질문에 대한 답은 많은 프로그래밍 질문과 마찬가지로 상황에 따라 다르다는 것입니다. 어느 한 쪽이 다른 쪽에 비해 분명한 이점이 있는 경우도 있지만, 대부분의 경우 서로 바꿔 사용할 수 있습니다.
이 글에서는 type과 interface의 주요 차이점과 유사점에 대해 논의하고 각 인터페이스를 사용하는 것이 적절한 시기를 살펴보겠습니다.
type과 interface의 기본 사항부터 시작해 보겠습니다.
type은 데이터의 모양을 정의하는 데 사용할 수 있는 TypeScript의 키워드입니다. 타입스크립트의 기본 유형은 다음과 같습니다:
각 기능에는 고유한 기능과 목적이 있으므로 개발자는 특정 사용 사례에 적합한 기능을 선택할 수 있습니다.
타입스크립트에서 타입 별칭은 "모든 타입의 이름"을 의미합니다. 별칭은 기존 유형에 새 이름을 만드는 방법을 제공합니다. Type aliases은 새로운 유형을 정의하는 것이 아니라 기존 유형에 대한 대체 이름을 제공할 뿐입니다.
Type aliases은 type 키워드를 사용하여 만들 수 있으며, 원시 유형을 포함하여 유효한 모든 TypeScript 유형을 참조할 수 있습니다.
type MyNumber = number;
type User = {
id: number;
name: string;
email: string;
}
위의 예에서는 두 개의 type aliases를 만듭니다: MyNumber와 User입니다. 숫자 유형에 대한 약어로 MyNumber를 사용하고, 사용자의 유형 정의를 나타내는 데 User type aliases을 사용할 수 있습니다.
“types versus interfaces”라고 할 때 실제로는 "type aliases versus interfaces"를 의미합니다. 예를 들어 다음과 같은 별칭을 만들 수 있습니다.
type ErrorCode = string | number;
type Answer = string | number;
위의 두 가지 type aliases은 동일한 유니온 유형인 string | number에 대한 대체 이름을 나타냅니다. 기본 유형은 동일하지만 서로 다른 이름은 다른 의도를 나타내므로 코드 가독성이 높아집니다.
타입스크립트에서 interface는 객체가 준수해야 하는 계약을 정의합니다. 아래는 그 예시입니다.
interface Client {
name: string;
address: string;
}
type annotations을 사용하여 동일한 Client 계약 정의를 표현할 수 있습니다.
type Client = {
name: string;
address: string;
};
위의 경우 type 또는 interface 중 하나를 사용할 수 있습니다. 그러나 interface 대신 type을 사용하는 것이 차이를 만드는 몇 가지 시나리오가 있습니다.
선언 병합은 interfaces 전용 기능입니다. 선언 병합을 사용하면 interface를 여러 번 정의할 수 있으며 TypeScript 컴파일러는 이러한 정의를 단일 interface 정의로 자동 병합합니다.
다음 예제에서는 두 개의 Client interface 정의가 TypeScript 컴파일러에 의해 하나로 병합되어 클라이언트 인터페이스를 사용할 때 두 개의 프로퍼티를 갖게 됩니다
interface Client {
name: string;
}
interface Client {
age: number;
}
const harry: Client = {
name: 'Harry',
age: 41
}
Type aliases은 같은 방식으로 병합할 수 없습니다. 위의 예에서와 같이 Client type을 두 번 이상 정의하려고 하면 오류가 발생합니다.

선언 병합을 적절한 위치에 사용하면 매우 유용하게 사용할 수 있습니다. 선언 병합의 일반적인 사용 사례 중 하나는 특정 프로젝트의 요구 사항에 맞게 타사 라이브러리의 유형 정의를 확장하는 것입니다.
선언을 병합해야 하는 경우 interface를 사용하는 것이 좋습니다.
interface 하나 또는 여러 개의 interface를 확장할 수 있습니다. extends 키워드를 사용하면 new interface가 기존 interface 모든 속성과 메서드를 상속하는 동시에 새 속성을 추가할 수 있습니다
예를 들어 Client interface 확장하여 VIPClient interface 만들 수 있습니다.
interface VIPClient extends Client {
benefits: string[]
}
type에 대해 유사한 결과를 얻으려면 교집합 연산자(intersection operator)를 사용해야 합니다:
type VIPClient = Client & {benefits: string[]}; // Client is a type
정적으로 알려진 멤버가 있는 type alias에서 interface를 확장할 수도 있습니다.
type Client = {
name: string;
};
interface VIPClient extends Client {
benefits: string[]
}
단, union types은 예외입니다. union types을 사용하면 여러 유형 중 하나가 될 수 있는 값을 기술하고 다양한 기본 유형, 리터럴 유형 또는 복합 유형의 유니온을 만들 수 있습니다.
interface에는 유니온 타입에 해당하는 것이 없습니다. 유니온 타입에서 interface를 확장하려고 하면 다음과 같은 오류가 발생합니다.
type Jobs = 'salary worker' | 'retired';
interface MoreJobs extends Jobs {
description: string;
}

이 오류는 유니온 유형을 정적으로 알 수 없기 때문에 발생합니다. interface 정의는 컴파일 시점에 정적으로 알려져 있어야 합니다.
Type aliases은 아래와 같이 교집합을 사용하여 interface를 확장할 수 있습니다.
interface Client {
name: string;
}
type VIPClient = Client & { benefits: string[]};
const test: VIPClient = {
benefits: ['dadad'],
name: 'asdasdsd'
}
간단히 말해, interfaces 와 type aliases 모두 확장할 수 있습니다. interfaces 는 정적으로 알려진 type aliases을 확장할 수 있고, type aliases은 교집합 연산자를 사용하여 인터페이스를 확장할 수 있습니다.
types 과 interfaces의 또 다른 차이점은 동일한 속성 이름을 가진 interfaces에서 확장을 시도할 때 충돌이 처리되는 방식입니다.
interfaces를 확장할 때 아래 예시에서와 같이 동일한 속성 키는 허용되지 않습니다.
interface Person {
getPermission: () => string;
}
interface Staff extends Person {
getPermission: () => string[];
}
충돌이 감지되어 오류가 발생합니다.

Type aliases는 충돌을 다르게 처리합니다. 동일한 속성 키를 가진 다른 유형을 확장하는 Type aliases의 경우 오류를 발생시키는 대신 모든 속성을 자동으로 병합합니다.
다음 예제에서는 교집합 연산자가 두가지 getPermission 선언의 메서드 서명을 병합하고 typeof operator를 사용하여 유니온 type 매개변수의 범위를 좁히므로 type에 안전한 방식으로 반환 값을 얻을 수 있습니다.
type Person = {
getPermission: (id: string) => string;
};
type Staff = Person & {
getPermission: (id: string[]) => string[];
};
const AdminStaff: Staff = {
getPermission: (id: string | string[]) =>{
return (typeof id === 'string'? 'admin' : ['admin']) as string[] & string;
}
}
두 속성의 유형 교집합은 예기치 않은 결과를 생성할 수 있다는 점에 유의해야 합니다. 아래 예제에서 확장 유형 Staff의 name property는 string과 number가 동시에 될 수 없기 때문에 never가 됩니다.
type Person = {
name: string
};
type Staff = Person & {
name: number
};
// error: Type 'string' is not assignable to type 'never'.(2322)
const Harry: Staff = { name: 'Harry' };
요약하면, interfaces는 컴파일 시 속성 또는 메서드 이름 충돌을 감지하여 오류를 생성하는 반면, type 교차에서는 오류를 발생시키지 않고 속성 또는 메서드를 병합합니다. 따라서 함수를 오버로드해야 하는 경우 type aliases을 사용해야 합니다.
TypeScript에서는 interface 또는 type alias을 사용하여 클래스를 구현할 수 있습니다.
interface Person {
name: string;
greet(): void;
}
class Student implements Person {
name: string;
greet() {
console.log('hello');
}
}
type Pet = {
name: string;
run(): void;
};
class Cat implements Pet {
name: string;
run() {
console.log('run');
}
}
위에 표시된 것처럼 인터페이스와 유형 별칭 모두 클래스를 구현하는 데 유사하게 사용할 수 있으며, 유일한 차이점은 유니온 유형을 구현할 수 없다는 점입니다.
type primaryKey = { key: number; } | { key: string; };
// can not implement a union type
class RealKey implements primaryKey {
key = 1
}

위의 예제에서는 클래스가 특정 데이터 형상을 나타내지만 유니온 type은 여러 data type 중 하나이기 때문에 TypeScript 컴파일러가 오류를 발생시킵니다.
타입스크립트에서 튜플 타입을 사용하면 요소 수가 고정된 배열을 표현할 수 있으며, 각 요소는 고유한 데이터 타입을 가집니다. 고정된 구조의 데이터 배열로 작업해야 할 때 유용할 수 있습니다
type TeamMember = [name: string, role: string, age: number];
interface는 Tuple types을 직접 지원하지 않습니다. 아래 예제처럼 몇 가지 해결 방법을 만들 수는 있지만, Tuple types을 사용하는 것만큼 간결하거나 가독성이 높지는 않습니다:
interface는 Tuple types을 직접 지원하지 않습니다. 아래 예제처럼 몇 가지 해결 방법을 만들 수는 있지만, Tuple types을 사용하는 것만큼 간결하거나 가독성이 높지는 않습니다.
interface ITeamMember extends Array<string | number>
{
0: string; 1: string; 2: number
}
const peter: ITeamMember = ['Harry', 'Dev', 24];
const Tom: ITeamMember = ['Tom', 30, 'Manager']; //Error: Type 'number' is not assignable to type 'string'.
TypeScript는 interface에서 찾을 수 없는 다양한 고급 유형 기능을 제공합니다. TypeScript의 고유한 기능 중 일부는 다음과 같습니다:
TypeScript의 타이핑 시스템은 새로운 릴리스가 나올 때마다 지속적으로 발전하여 복잡하고 강력한 도구 상자가 되었습니다. 인상적인 타이핑 시스템은 많은 개발자가 TypeScript를 선호하는 주된 이유 중 하나입니다.
type aliases과 interface는 매우 유사하지만 이전 섹션에 표시된 것처럼 몇 가지 미묘한 차이가 있습니다.
거의 모든 interface 기능이 types으로 제공되거나 이와 동등한 기능이 있지만 선언 병합이라는 한 가지 예외가 있습니다. 일반적으로 interface는 기존 라이브러리를 확장하거나 새 라이브러리를 작성하는 등 선언 병합이 필요한 시나리오에서 사용해야 합니다. 또한 객체 지향 상속 스타일을 선호하는 경우 interface와 함께 확장 키워드를 사용하는 것이 type aliases과 교차점을 사용하는 것보다 가독성이 더 좋은 경우가 많습니다.
그러나 타입의 많은 기능은 interface는 구현하기 어렵거나 불가능합니다. 예를 들어 타입스크립트는 조건부 타입, 일반 타입, 타입 가드, 고급 타입 등과 같은 풍부한 기능을 제공합니다. 이러한 기능을 사용하여 잘 제약된 유형 시스템을 구축하여 앱의 유형을 강력하게 만들 수 있습니다. 이는 interface는 달성할 수 없는 기능입니다.
대부분의 경우 개인 취향에 따라 두 가지를 혼용하여 사용할 수 있습니다. 하지만 다음과 같은 사용 사례에서는 type aliases을 사용해야 합니다.
인터페이스에 비해 타입은 표현력이 더 풍부합니다. 인터페이스에서는 사용할 수 없는 많은 고급 유형 기능이 있으며, 이러한 기능은 TypeScript가 발전함에 따라 계속 증가하고 있습니다.
또한 함수형 프로그래밍 패러다임과 잘 어울리기 때문에 많은 개발자가 타입을 선호합니다. 풍부한 타입 표현식을 사용하면 함수형 구성, 불변성 및 기타 함수형 프로그래밍 기능을 타입 안전 방식으로 더 쉽게 달성할 수 있습니다.
이 글에서는 유형 별칭과 인터페이스의 차이점에 대해 설명했습니다. 어느 한 쪽이 다른 쪽보다 선호되는 경우도 있지만, 대부분의 경우 두 가지 중 하나를 선택하는 것은 개인적인 취향에 달려 있습니다.
typescript 3.8에서 나오게된 문법입니다. type only import/export 장점은 아래와 같습니다.
//있을 경우
export type ShowAlert = {
isOpen: boolean
content: ReactNode
buttonText?: string
submitColor?: ButtonColor
submitOutline?: boolean
onSubmit?: () => void
}
import { useToggleBodyScroll } from '@/common/hooks'
import type { ShowAlert } from '@/common/hooks'
여기서 type aliases과 interface 둘 다 type only import/export가 가능합니다.