TS) Interface 와 Type 확장에서의 차이점 알아보기

숩딩·2023년 3월 1일
8

Interface vs Type

typeScript 로 작업을 하다가 둘의 정확한 차이는 무엇인가,
타입으로 할 때는 오류가 나고 인터페이스로 할 때는 오류가 안나는 경우가 생기면서
둘의 차이점을 파헤쳐 보기로 했다.

일단 일반적으로 interface 와 type 차이점을 검색하면 나오는 내용들은 비슷하다.

  • interface는 객체에서만 사용이 가능하다.

  • computed value 사용은 type만 가능

    type names = 'firstName' | 'lastName'
    
    type NameTypes = {
      [key in names]: string
    }
    
    const test: NameTypes = { firstName: 'su', lastName: 'lee' }
    
    interface NameInterface {
      // error
      [key in names]: string
    }

  • 확장의 방법
    Interface 는 extends 를 통해 선언적 확장이 가능하다. 즉, 동일한 이름으로 interface를 선언하는 것이 가능하다.
    type 은 & 를 통해 타입을 확장한다.

확장에서의 차이점

내가 궁금한 점은 확장에서의 차이점이었다.

interface IBase {
  prop_a: string;
  prop_b?: string;
  proc_c: string;
}

interface IExtendBase extends IBase {
  prop_a?: string;
  prop_b: string;
  prop_new: string;
}

먼저 interface에서의 확장을 해보자
이렇게 작성했을 경우

이러한 에러메세지가 뜬다.
이 에러가 나오는 이유는, prop_aIExtendBaseprop_a 를 덮어쓰지 못하기 때문이다.
IExtendBase 에 있는 props_a 는 옵셔널이기 때문에 IBase에 있는 prop_a보다 더 넓은 범위의 타입을 갖는다. 그렇기 때문에 IBasestring 값만 갖는 prop_a 는 확장을 하지 못해 string 형식에 할당할 수 없다는 에러가 난다.
비슷한 사례로 또 다른 예시를 들어보면,

interface IExtendBase extends IBase {
    prop_a: string | number;
    prop_b: string;
    prop_new: string;
}


IExtendBaseprop_astring | number 를 갖는데, IBasestring 값만 갖기 때문에 형식에 할당할 수 없다는 에러가 나온다.

그렇다면 prop_a 의 타입을 변경해주고 싶을 땐 어떻게 해야 하나..?

interface IExtendBase extends Omit<IBase, "prop_a"> {
  prop_a?: string;
  prop_b: string;
  prop_new: string;
}

omit 을 사용해서 해결해 줄 수 있다 !

직접 타입을 보면

type Test<T> = {
  [k in keyof T]: T[k];
};

type test = Test<IExtendBase>;

옵셔널을 붙여준 prop_a 타입이 잘 들어간 것을 볼 수 있다.

type도 똑같이 작성해 보자

type TBase = {
  prop_a: string;
  prop_b?: string;
  prop_c: string;
};

type TExtendBase = TBase & {
  prop_a?: string;
  prop_b: string;
  prop_new: string;
};

어라라 인터페이스와 같이 작성했음에도 불구하고 에러가 나지 않는다 !

그리고 타입을 보니 옵셔널은 되지 않고 string만 남아있는 것을 볼 수 있다.

왜 타입은 되는 걸까?
타입의 확장은 두 타입에 포함된 모든 속성을 가진 새 타입을 생성하기 때문이다. 교집합 만을 가진다고 생각하면 된다.
예를 들어 TExtendBaseprop_a 가 더 넓은 값(string|number)의 타입을 가질 경우, TBasestring 타입만을 가진다.

type TExtendBase = TBase & {
  prop_a: string | number; 
  prop_b: string;
  prop_new: string;
};

(string 만을 가지는 것을 볼 수 있음 )

다이어그램 이미지를 통해 더 쉽게 이해할 수 있다.
TypeA 에서 prop_a:string | boolean , TypeB에서 prop_a: boolean | number 인데 이걸 합친 TypeC는 교집합인 prop_aboolean 값만을 가지는 것을 볼 수 있다.

확장한 타입에서 prop_a 의 타입을 더 확장해주고 싶을 경우에는 어떻게 해줘야 할까? 인터페이스와 비슷하게 omit을 통해 해줄 수가 있다.

type TBase = {
  prop_a: string;
  prop_b?: string;
  prop_c: string;
};

type TExtendBase = Omit<TBase, "prop_a"> & {
  prop_a?: string;
  prop_b: string;
  prop_new: string;
};

prop_a 를 옵셔널로 만들어 주고자 omit으로 prop_a의 결합을 빼고, 새로운 타입을 입력하여 옵셔널로 확장할 수 있다.

둘의 확장에 대한 차이에 대해 알아 보았는데, 궁금했던 점이 좀 해결되었다..!

출처 : https://smnh.me/extending-typescript-interfaces-and-type-aliases-with-common-properties

profile
Front-End Developer ✨

0개의 댓글