Front-End/TypeScript

[TypeScript] 타입(type)과 인터페이스(interface)의 차이점

Y0ungZ 2023. 12. 22. 23:45
🍀 목차
확장
새 필드 추가
사용 범위
Index Signature
type으로만 선언 가능한 타입들

 

 타입(type)과 인터페이스(interface)는 TypeScript에서 객체의 타입을 만드는 데 사용된다. type alias로는 객체 타입뿐 아니라 모든 유형의 타입을 만들 수 있는데, 이처럼 두 키워드의 특징과 차이점을 알아보자.

 

확장

type(교차 타입 사용)

type Animal = {
    name: string   
    sound: string
}

type BearFeature = {
    honey: boolean
    sound: string
}

type Bear = Animal & BearFeature;

const bear:Bear = {name:"Pooh",sound:"kkao~",honey:true};
bear.name;
bear.honey;
bear.sound;

 

 

 타입은 &(intersection)을 활용하여 확장이 가능하다. 집합으로 생각해 보면 타입 Animal, 타입 BearFeature를 모두 만족하는 경우라고 생각하면 이해가 쉽다.

 

intersection
intersection

 

 

 교차 타입(intersection)을 사용하면 여러 타입을 합쳐 하나의 타입으로 사용할 수 있다. type Bear = Animal & BearFeature; Bear 타입의 모든 값은 Animal 타입의 값이면서 BearFeature 타입의 값이다.

 

type Parent = {
    printName: (name: number) => void;
}

type Child = Parent & {
    // (name: number | string) => void 와 같아진다.
    printName: (name: string) => void;
}

const test: Child = {
    printName: (name: number | string) => {
        console.log('name: ' , name);
    },
};

test.printName(1); 
test.printName('1');

 

 

 

interface(extends 사용)

interface Animal {
    name: string
}

interface Bear extends Animal {
    honey: boolean
}

const bear: Bear = {name:'fubao', honey:false};
bear.name;
bear.honey;

 

 인터페이스는 extends 키워드를 사용하여 확장이 가능하다. 

 

interface Parent {
    printName: (name: number) => void;
}

// Error. Interface 'Child' incorrectly extends interface 'Parent'.
interface Child extends Parent {
    printName: (name: string) => void;
}

// 그대신 아래와 같은 상속이 가능하다.

interface Child extends Parent {
    printName: (name: string | number) => void;
}

// 라이브러리를 만들고 open API를 제공하면 사용자가 인터페이스의 해당 특징을 사용하여 확장할 수 있어 편하다.

 

 

 인터페이스들을 교차 타입으로 만들어 type alias로 선언하는 것도 된다.

 

// 이런 것이 불가능
interface Animal & BearFeature {
    name: string
    sound: string
}

// 이런 건 가능
interface Animal {
    name: string   
    sound: string
}

interface BearFeature {
    honey: boolean
    sound: string
}

type Bear = Animal & BearFeature;

 


type Foo = Bar & Baz & {
	someProp: string;
}

interface Foo extends Bar, Baz {
	someProp: string;
}

 

 

Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never.

https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections

 

 확장에 있어서 인터페이스는 중복으로 선언된 충돌을 해결하기 위해 단순 객체 타입을 생성한다. 인터페이스는 객체만 정의하기에 합치기만 하면 되지만, 타입의 경우보다 다양한 타입이 오기에 이를 재귀적으로 순환하며 속성을 merge하게 된다. 이 과정에서 호환되지 않는 타입들이 교차되는 등 특정 상황이 발생하여 TypeScript에서 값의 공집합을 뜻하는 never타입이 나올 수도 있다.

 

type type1 = {a:1; b:2} & {b:3};

 

특정 코드의 인터섹션 실행 결과. never이 나왔다.
never이 나온다.

 

이렇게 제대로 merge가 되지 않을 수도 있다. 객체에서만 쓰는 용도라면 인터페이스를 쓰는 것이 좋다.

 

 

새 필드 추가

type

type Window = {
    title: string
}

type Window = {
    ts: TypeScriptAPI
}

// Error: Duplicate identifier 'Window'.
// 병합되지 않는다. 고유해야 한다.

 

interface

 선언적 확장이 가능하다.

interface Window {
    title: string
}

interface Window {
    ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});

 

 

 편리한 확장이지만 한편으로는 동일 스코프에 동일한 이름의 타입을 중복으로 할당한다는 점이 문제 발생의 원인이 될 수 있으므로 ESLint로 변수 재선언을 허용하지 않는 no-redeclare을 활성화시킬수도 있다(해당 규칙으로 확인된 문제는 TS 컴파일러에서 자동으로 확인되지만 컴파일러 오류 메시지보다 ESLint 오류 메시지를 선호하는 경우에는 활성화시키면 된다).

 

사용 범위

type

객체 O, 원시타입 O

type AnObject1 = {
    value: string
}

// 객체 사용 가능

type SanitizedString = string
type EvenNumber = number

// primitive 타입을 활용하여 사용자 정의 이름 만들기 가능

 

interface

객체 O, 원시타입 X

interface AnObject1 {
    value: string
}

// 객체 사용 가능

interface X extends string {}

// interface는 primitive 타입을 활용한 사용자 정의가 가능하지 않다.

 

Index Signature

인덱스 시그니처(Index Signature)는 객체 내부에 [Key: T]: U 형식으로 선언하며, 해당 타입의 속성 키는 모두 T, 속성 값은 U를 가져야 함을 의미한다. 객체가 Key, Value의 형식, 각각의 타입을 명확히 해야 하는 경우 사용한다. 

 

타입은 암묵적 인덱스 시그니처를 갖는다.

// KnownAttributes가 type이라면 오류가 나지 않는다.
interface KnownAttributes{
    x:number
    y:number
}

const knownAttributes: KnownAttributes={
    x:1,
    y:2
}

type RecordType = Record<string, number>;


//Type 'KnownAttributes' is not assignable to type 'RecordType'.
//Index signature for type 'string' is missing in type 'KnownAttributes'.
const temp: RecordType = knownAttributes;

 

 

인터페이스는 추후 확장될 수 있기에 인덱스 시그니처를 활용하려면 명시적으로 정의해줘야 한다.

 

interface KnownAttributes {
    x:number
    y:number
    [z: number]:string;
}

const knownAttributes: KnownAttributes={
    x:1,
    y:2,
    1:'z'
}


const temp: KnownAttributes = knownAttributes;

 

 

 

마지막으로 타입으로만 가능한 것들을 알아보자.

type으로만 선언 가능한 타입들

Union 타입

 

교차 타입이 타입 A와 타입 B 모두 만족이라는 것이라면 유니온 타입은 OR연산자(||)처럼 타입 A거나 타입 B이다의 의미를 가진다. 

type Fruit = 'apple' | 'lemon';
type Vegetable = 'potato' | 'tomato';

type Food = Fruit | Vegetable;
// 'apple' | 'lemon' | 'potato' | 'tomato'

// Union Type은 type alias를 통해서만 정의 가능하다.

 

Tuple 타입

튜플(Tuple)은 배열 타입을 특수하게 사용할 수 있는 타입이다. 배치 순서, 타입 모두 튜플에 명시된 대로 정의해야 한다. 

type Animal = [name: string, age: number];
const cat: Animal = ['', 1];

// Tuple Type은 type alias를 통해서만 정의 가능하다.

 

 

Mapped 타입

맵드(Mapped) 타입은 기존 정의된 타입을 새로운 타입으로 변환해 주는 문법을 의미한다. JavaScript의 map 메서드를 생각하면 된다.

type Vegetable = 'potato' | 'tomato';

//type VegetableOption = {
//    potato: boolean;
//    tomato: boolean;
//}
type VegetableOption = {
    [Property in Vegetable]: boolean;
}

type VegetableOption = {
    [Property in Vegetable]: boolean;
}

const option: VegetableOption = {
	potato: true,
    tomato: false,
};

// 'potato' | 'tomato'
type VegetableAlias = keyof VegetableOption;

 

 

Unknown 타입

 알려지지 않은 타입이 있다면 typeof를 사용하여 타입확인이 가능하다.

const potato = {name: 'potato', weight: 1};

// type Vegatable = {
// name: string;
// weight: number;
// }
type Vegetable = typeof potato;

 

 

 

참고자료

https://www.typescriptlang.org/ko/docs/handbook/2/everyday-types.html

https://betterprogramming.pub/differences-between-type-aliases-and-interfaces-in-typescript-4-6-6489246d4e48

https://yceffort.kr/2021/03/typescript-interface-vs-type

https://devocean.sk.com/blog/techBoardDetail.do?ID=165230&boardType=techBlog