-
[TypeScript] 타입(type)과 인터페이스(interface)의 차이점Front-End/TypeScript 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)을 사용하면 여러 타입을 합쳐 하나의 타입으로 사용할 수 있다.
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};
이렇게 제대로 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://yceffort.kr/2021/03/typescript-interface-vs-type
https://devocean.sk.com/blog/techBoardDetail.do?ID=165230&boardType=techBlog