[Typescript] 타입 - type vs interface

mainsain·2024년 2월 8일
1

Typescript

목록 보기
2/8
post-thumbnail

type과 interface를 왜 사용해야할까

흔히 객체를 타이핑하기 위해 자주 사용되는 키워드로 type, interface가 있다. 타입을 매번 일일이 지정하기엔, 중복적인 요소가 너무 많기에 이를 활용하여 중복 없이 타입을 쓸 수 있다.

type AType = {
  title: string;
  description: string;
};

interface AInter {
  title: string;
  description: string;
}
const bType: AType = { /* ... */ };
const bInter: AInter = { /* ... */ };

ㅇㅇ 그렇구나. 근데 그러면 type이랑 interface랑 뭐가달라?

type vs interface

type의 정의를 알아보자.

type을 사용하면, 모든 타입(any type)에 네이밍을 할 수 있다. object가 아니여도 가능하며, type ID = number | string;의 경우처럼 유니온 타입의 경우도 가능하다.

interface는?

object type을 만드는 또 다른 방법이라고 한다. (interface는 type 처럼 union, tuple타입을 정의할 수 없다.)

그러면 명확한 interface와 type의 차이점을 알 수 있다.

type : 모든 타입에 이름을 달아줄 수 있다.
interface : 객체 타입에만 이름을 달아줄 수 있다.

TS handbook에서 나오는 차이점

둘이 매우 유사하기에, 일반적으로 둘 중 하나 자유롭게 선택하라고 한다.
type : cannot be re-opened to add new properties.
interface : is always extendable.

선언 병합(Declaration Merging)의 차이

선언 병합이란, 컴파일러가 같은 이름으로 선언된 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 alias는 extends가 안된다?

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를 사용한다.

✅ 함수의 type을 나타내기에 더 직관적이다.

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가 실제 함수의 모양과 비슷해보여서 더 낫다고 판단했다고 한다. 이는 고차함수일 때 더 명확히 차이를 보인다.

✅ type alias가 IDE에서 미리보기를 더 잘 지원한다.

interface에선 미리보기가 지원되지 않는다. (지금도 내가 해보니 똑같이 안보인다.)

✅ 원치 않은 선언 병합을 막아준다.

위에서 정의했듯, interface와 type의 차이점 중 하나가 선언 병합의 가능성이다.

배달의 민족에선, 선언 병합은 의도치 않은 에러가 발생될 수 있기에 type alias를 통해 선언병합을 의도적으로 방지했다.

🎙️ 우아한 형제들 인터뷰 (우아한 타입스크립트 with 리액트)

❓ type과 interface를 둘 다 쓸 수 있는 상황에서 팀은 주로 어떤 것을 사용하나요?

배달이팀 : 정해진 컨벤션이 있는 건 아닌데, 대부분의 상황에서 interface를 사용합니다. 만약 컨벤션을 정해야한다면, 공식 문서에 나와있는 내용을 바탕으로 전역적으로 사용할 땐 interface, 작은 범위내에서 한정적으로 사용할 땐 type을 써도 되지않을까 생각이 드네요

냥이팀 : type과 interface 둘 다 사용하고 있습니다. type은 어떤 값에 대한 정의같이 정적으로 결정되어 있는 것을, interface는 확장될 수 있는 basis를 정의하거나 어떤 object 구성을 설명하는 요소라고 생각해요.

봉다리팀 : 필요에 따라 모두 사용합니다. 예를 들어 선언 병합이 필요하면 interface를 사용하고, computed value를 사용해야 한다면 type 정의를 쓰고 있어요.

왕팀 : interface의 필요성을 못느껴서 type정의를 주로 사용합니다. IDE에서 type은 리터럴한 값이 직접 노출되지만, interface는 아니거든요. 더 쉽게 타입추론이 가능해서 type을 선호했습니다.

❓ 팀 내에서 type이나 interface만을 써야 하는 상황이 있었나요?

배달이팀 : 객체지향적으로 코드를 짤 때, 특히 상속하는 경우에 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

profile
새로운 자극을 주세요.

0개의 댓글