흔히 객체를 타이핑하기 위해 자주 사용되는 키워드로 type, interface가 있다. 타입을 매번 일일이 지정하기엔, 중복적인 요소가 너무 많기에 이를 활용하여 중복 없이 타입을 쓸 수 있다.
type AType = {
title: string;
description: string;
};
interface AInter {
title: string;
description: string;
}
const bType: AType = { /* ... */ };
const bInter: AInter = { /* ... */ };
ㅇㅇ 그렇구나. 근데 그러면 type이랑 interface랑 뭐가달라?
type을 사용하면, 모든 타입(any type)에 네이밍을 할 수 있다. object가 아니여도 가능하며,
type ID = number | string;
의 경우처럼 유니온 타입의 경우도 가능하다.
object type을 만드는 또 다른 방법이라고 한다. (interface는 type 처럼 union, tuple타입을 정의할 수 없다.)
그러면 명확한 interface와 type의 차이점을 알 수 있다.
type : 모든 타입에 이름을 달아줄 수 있다.
interface : 객체 타입에만 이름을 달아줄 수 있다.
둘이 매우 유사하기에, 일반적으로 둘 중 하나 자유롭게 선택하라고 한다.
type : cannot be re-opened to add new properties.
interface : is always extendable.
선언 병합이란, 컴파일러가 같은 이름으로 선언된 2개의 선언을, 하나의 정의로 합치는 것을 의미한다.
몇개든 병합할 수 있다.
이 예시가 type과 interface의 차이를 명확하게 보여준다.
interface : 선언 병합이 가능하다.
type : 선언 병합이 불가능하다.
더 쉬운 예시로,
// type AType = {
// title: string;
// description: string;
// };
// type AType = { // Duplicate identifier 'AType'.ts(2300)
// test : string;
// };
interface AInter {
title: string;
description: string;
}
interface AInter {
test : string;
}
const inter : AInter = {
title: 'title',
description: 'description',
test: 'test'
};
console.log(inter); // { title: 'title', description: 'description', test: 'test' }
이처럼 interface는 AInter를 확장시켜서 사용가능한 특징이 있다.
type AType = {
title: string;
description: string;
};
interface AInter {
title: string;
description: string;
}
interface ExType extends AType {
test: string;
}
interface ExInter extends AInter {
test: string;
}
const NewType: ExType = {
title: "title",
description: "description",
test: "test",
};
const NewInter: ExInter = {
title: "title",
description: "description",
test: "test",
};
console.log(NewType); // { title: 'title', description: 'description', test: 'test' }
console.log(NewInter); // { title: 'title', description: 'description', test: 'test' }
type vs interface를 찾아보다가 type은 extends가 안된다는 글이 있었다. 예전엔 실제로 안된 것 같은데, 글을 작성하는 지금의 Typescript는 type을 통해 만든 타입도 확장이 가능하다. 따라서 차이점으로 이 부제목을 제시하는 것은 틀린 설명이다.
https://techblog.woowahan.com/9804/#toc-3
결론 : 해당 팀에선 type alias를 사용한다.
const getDoubleString = (id: number): string => String(id * 2)
type TGetDoubleString = (id: number) => string
const getDoubleString1: TGetDoubleString = (id) => String(id * 2)
interface IGetDoubleString {
(id: number): string;
}
const getDoubleString2: IGetDoubleString = (id) => String(id * 2)
보다시피 type alias가 실제 함수의 모양과 비슷해보여서 더 낫다고 판단했다고 한다. 이는 고차함수일 때 더 명확히 차이를 보인다.
interface에선 미리보기가 지원되지 않는다. (지금도 내가 해보니 똑같이 안보인다.)
위에서 정의했듯, interface와 type의 차이점 중 하나가 선언 병합의 가능성이다.
배달의 민족에선, 선언 병합은 의도치 않은 에러가 발생될 수 있기에 type alias를 통해 선언병합을 의도적으로 방지했다.
배달이팀 : 정해진 컨벤션이 있는 건 아닌데, 대부분의 상황에서 interface를 사용합니다. 만약 컨벤션을 정해야한다면, 공식 문서에 나와있는 내용을 바탕으로 전역적으로 사용할 땐 interface, 작은 범위내에서 한정적으로 사용할 땐 type을 써도 되지않을까 생각이 드네요
냥이팀 : type과 interface 둘 다 사용하고 있습니다. type은 어떤 값에 대한 정의같이 정적으로 결정되어 있는 것을, interface는 확장될 수 있는 basis를 정의하거나 어떤 object 구성을 설명하는 요소라고 생각해요.
봉다리팀 : 필요에 따라 모두 사용합니다. 예를 들어 선언 병합이 필요하면 interface를 사용하고, computed value를 사용해야 한다면 type 정의를 쓰고 있어요.
왕팀 : interface의 필요성을 못느껴서 type정의를 주로 사용합니다. IDE에서 type은 리터럴한 값이 직접 노출되지만, interface는 아니거든요. 더 쉽게 타입추론이 가능해서 type을 선호했습니다.
배달이팀 : 객체지향적으로 코드를 짤 때, 특히 상속하는 경우에 interface를 사용했던 것 같습니다. 예를 들어 extends나 implements를 사용할 때요.
냥이팀 : 유니온 타입이나 교차 타입 등 type 정의에서만 쓸 수 있는 기능을 활용할 때 type을 사용했습니다. interface 키워드는 같은 속성을 공유하는 기준 인터페이스를 정의하고 확장할 때 사용했어요
메이팀 : props에 Recode 형식을 extends할 때 interface로 선언된 변수를 넣으면 에러가 발생해서 type으로 바꿔 넣은 경험이 있습니다. 예를 들어 표 컴포넌트를 만들어서 쓸 때, 배열로 되어있는 데이터를 넣으면 피드 이름이 특정되지 않은 경우가 있어요. 이럴 때 Record로 선언해서 컴포넌트를 사용했는데 interface처럼 인덱스 키가 따로 설정되지 않으면 오류가 발생하더라구요
봉다리팀 : computed value를 써야 했을 때 type 정의를 사용했습니다.
https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
https://devocean.sk.com/blog/techBoardDetail.do?ID=165230&boardType=techBlog
https://www.typescriptlang.org/docs/handbook/declaration-merging.html
https://medium.com/hcleedev/typescript-type과-interface는-어떻게-다를까-c4ca65a0257
https://techblog.woowahan.com/9804/#toc-3