🗓️ 2023. 09. 29
⏱️ 8

Type과 Interface

알아서들 쓰시면 됩니다

type이랑 interface는 뭐가 다르지? 언제 써야 되지?

타입스크립트를 처음 배울 때 누구나 떠올릴 법한 질문이다.
너무 많이 다뤄진 주제라서 포스팅할까 말까 고민했는데
언젠가 만나게 될 내 동료와 논의할만한 얘기도 적어두면 좋을 것 같았다.

무적권 type을 쓰십쇼(?)

사실 내 유튜브 알고리즘에 요런 영상이 떠서 작성하게 됐다.

'타입스크립트에서 interface가 아닌 type을 사용하라'는 영상인데 요점은 대략 이렇다.
interface는 type에 비해,

  1. 표현할 수 있는 범위가 좁으며
  2. 오용될 여지가 있고
  3. 문법이 못 났다(?)

그럼 영상에서 언급된 주요 차이점들을 확인해보자.

type과 interface 비교

표현 범위와 방식

  • interface는 primitive 타입을 표현할 수 없다.

    type Name = string; // 가능
    interface Name extends string {}; // 불가능
    

    interface는 명명된 타입(named type)이나 클래스만 확장할 수 있기 때문에
    굳~이 표현하고 싶다면 기본 클래스인 String을 확장해야 한다.

    interface Name extends String {} // 가능
    
  • interface는 union 타입을 표현할 수 없다.

    type Age = number | string;
    
    const age1: Age = 12;
    const age2: Age = "12";
    
  • type은 intersection으로 확장한다.

    type Account = {
        name: string;
    };
    
    type Character = Account & {
        move(): void;
    };
    

    interface는 extends로 확장한다.

    interface Account {
        name: string;
    };
    
    interface Character extends Account {
        move(): void;
    };
    
  • tuple을 표현할 때 interface에선 property까지 정의해줘야 한다.

    type Coordinates = [number, number];
    
    interface Coordinates extends Array<number> {
        0: number;
        1: number;
    };
    

선언 병합(declaration merging)

공식 문서에 따르면 interface는 확장가능성에 열려있으나 type은 닫혀있다.

the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

그래서 interface는 동일한 식별자를 중복 선언함으로써 확장될 수 있지만 type은 불가능하다.

// Interface
interface Account {
    id: string;
    email: string;
}

interface Account {
    password: number;
}

const account: Account = {
    id: 'id',
    email: 'test@test.com',
    password: '1q2w3e4r!!'
}; // 확장됨


// Type
type Account = {
    id: string;
    email: string;
};

type Account = {
    password: number;
}; // 오류 발생

언뜻 보기엔 interface가 더 유용해보인다.
하지만 동일한 타입 식별자가 프로젝트 여기저기서 확장될 수 있음을 의미하며, 코드의 관리 포인트가 분산되는 문제로 이어질 수 있다.
결국 프로젝트 규모가 커지고 인원이 늘어날수록 타입 오류를 추적하기 어려워진다.

영상 속에서 언급되기도 했던 유일한 좋은 용례(good case)는 내가 작성한 타입이 아니라 외부에서 가져온 타입을 확장해야 할 때이다.
가장 와닿는 확장 예시는 요런 경우일 것이다.

  • 서버에서 Request 타입에 user 프로퍼티를 추가해서 req.user 사용
  • 웹브라우저에서 Window 타입에 새로운 프로퍼티 추가

못난 문법

영상 속에선 interface를 사용하면 타입을 추출하거나 유틸리티 타입을 사용할 때 {}가 붙는다는 이유로 문법적으로 못 났다고 말한다.

type User = {
    email: string;
    password: string;
    profile: {
        bio: string;
    }
};

type UserWithoutPasword = Omit<User, "password">;
interface UserWithoutPasword extends Omit<User, "password"> {};

const user: User = {
    email: "test@test.com";
    password: "1q2w3e4r!!";
    profile: {
        bio: "안녕하세요";
    }
}

type Profile = typeof user["profile"];
interface Profile extends typeof user["profile"] {};

공식 문서에선?

공식 문서에서는 아래와 같이 말한다.

A type alias is exactly that - a name for any type.

type은 모든 타입을 위해 이름을 붙이는 것

An interface declaration is another way to name an object type

interface는 object 타입에 이름을 붙이는 다른 방법

Type aliases and interfaces are very similar, and in many cases you can choose between them freely.

type과 interface는 아주 닮아있고, 대부분의 경우 원하는 걸로 자유롭게 쓰면 된다.

쓰읍...난 좀 반댄데..

기능성과 일관성을 위해 type을 사용하자는 유튜버의 의견에 동의하긴 어렵다.

  1. primitive와 union
    interface는 정의할 수 없는 타입들이기 때문에 애초에 고민하고 말고가 없다.

  2. 선언 병합
    이론적으론 앞서 언급했듯이 프로젝트의 복잡성을 야기할 수 있다.
    하지만 실질적으로 선언 병합이 문제가 됐던 적은 한 번도 없었다.
    문제가 되기는커녕 라이브러리의 타입을 확장할 때 빼고는 사용해 본 적도 없다.
    다른 프로그래밍 언어와 마찬가지로 같은 타입을 여러 곳에서 중복 선언한다는 것 자체가 사실 의아한 행위이다.
    (물론 접해본 언어가 타입스크립트뿐이라면 충분히 오용될 수 있다고 생각한다.)

  3. 못난 문법
    문법으로 인해 붙는 {}는 결코 못나지 않았다.
    다른 객체지향 언어를 접해본 적 있다면 오히려 이 편이 더 자연스럽다.
    tuple 또한 애초에 값을 할당할 때부터 배열 형식으로 사용하기 때문에 사고 흐름이 자연스레 type으로 넘어가게 된다.
    (interface로 선언된 튜플 타입 역시 아직 한 번도 본 적 없다.)

그리고 무엇보다 팀 내 컨벤션을 우선시해야 한다.
해당 영상의 댓글에서도 그런 컨벤션의 예시들을 확인할 수 있다.

  • 다른 객체지향 언어처럼 클래스를 구현(implementation)할 땐 interface 사용
  • 데이터의 구조를 표현할 땐 type을 사용

공식 문서에서도 특별히 type으로만 사용할 수 있는 기능이 아니라면 자유로이 선택하면 된다고 말하고 있고,
위에 나온 예시들처럼 그 자유 속에서 팀의 규칙을 마련해가면 된다.
코드 컨벤션을 갖춰나갈 땐 각 언어에서 특수하게 사용되는 요소보다 대부분의 언어에 통용되는 보편적 요소가 더 중요하다고 생각한다.

돌아가기
© 2024 VERYCOSY.