하루살이 개발일지

[TypeScript] type과 interface의 차이점 본문

웹개발/TypeScript

[TypeScript] type과 interface의 차이점

harusari 2023. 7. 13. 20:04

Type Aliases 에 대하여

TypeScript에서의 기본 type

  • 타입스크립트에 존재하는 기본 타입
String
Boolean
Number
Array
Tuple
Enum
Advanced types

 

타입 별칭(Type Aliases)

  • 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수
  • type 키워드를 붙여 만듦
  • 새로운 타입 값을 생성하는 것이 아닌 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 ‘이름’을 부여한 것과 같음
type MyNumber = number;

type User = {
  id: number;
  name: string;
  email: string;
}

출처: https://joshua1988.github.io/ts/guide/type-alias.html#%ED%83%80%EC%9E%85-%EB%B3%84%EC%B9%AD%EC%9D%98-%ED%8A%B9%EC%A7%95

 

Type Aliases의 특징

type ErrorCode = string | number;
type Answer = string | number;
  • union ( | ) 을 사용해 두 개 이상의 원시 타입을 결합할 수 있음
    • interface 에는 이 기능이 없음
  • 현재 ErrorCode와 Answer은 같은 union type ( string | number ) 이지만, 이렇게 구분지음으로써 다른 의도로 사용될 것을 암시 ⇒ 코드 가독성이 높아짐

 

Interfaces에 대하여

객체의 타입을 정의하는 interface

interface Client { 
    name: string; 
    age: number;
}

//interface를 바탕으로 객체 생성
let user: Client = {
  name: "John Doe",
  age: 30
};
  • interface는 객체가 어떤 프로퍼티를 가지고, 각 프로퍼티의 타입은 무엇인지 명시함
type Client = {
    name: string;
    address: string;
};
  • 이는 type 을 사용해서도 구현할 수 있음

 

type과 interface의 차이점

 

type을 쓰는 게 더 좋은 경우

1. 원시형 타입 (primitive types)

  • 원시형 타입 : number, string, boolean, null, undefined 타입
type Address = string;

type NullOrUndefined = null | undefined;
  • type alias를 이용하면 원시형 타입, union 타입을 쉽게 정의할 수 있음
type WindowStates = "open" | "closed" | "minimized"; // 가능
interface WindowStates = "open" | "closed" | "minimized"; // 에러
  • interface에서는 이것이 불가능
    • interface는 객체 유형만 사용 가능하기 때문

 

2. Union types

  • Union types : TypeScript에서 특정 변수가 여러 타입 중 하나를 가질 수 있는 것을 표현하는 방법
    • | 문자를 사용해 만듦
type Transport = 'Bus' | 'Car' | 'Bike' | 'Walk';
  • Transport 타입의 면수가 'Bus', 'Car', 'Bike', 'Walk' 중 하나만 가질 수 있음을 의미
interface CarBattery {
  power: number;
}

interface Engine {
  type: string;
}

type HybridCar = Engine | CarBattery;
  • 인터페이스를 사용해 Union type을 직접 만드는 것은 불가능
  • 하지만, 두 개 이상의 인터페이스를 결합해 새로운 유니온 타입을 생성하는 것은 가능
  • CarBattery 인터페이스와 Engine 인터페이스를 결합해 HybridCar 유니온 타입을 만들 수 있음
  • HybridCar 유니온 타입의 변수
    • Engine 인터페이스 또는 CarBattery 인터페이스 형태를 가질 수 있음
    • 즉, 하이브리드 자동차가 엔진 또는 배터리로 작동할 수 있다는 의미와 동일

 

3. Function types

  • 타입스크립트에서 함수 타입을 정의하려면 매개변수와 반환 타입을 지정해야 함
type AddFn =  (num1: number, num2:number) => number;

interface IAdd {
   (num1: number, num2:number): number;
}
  • type과 interface는 모두 함수 타입을 정의할 수 있음
    • 인터페이스는 : 를 사용하고, type은 ⇒ 를 사용함
  • type이 더 짧고 가독성이 좋아 type이 선호됨 (무조건적은 아님!)
  • type을 쓰는 또 다른 이유
    • 함수가 복잡해지면 → 조건부 타입(conditional types), 매핑된 타입(mapped types) 등 고급 타입 기능을 활용할 수 있어서
type Car = 'ICE' | 'EV';
type ChargeEV = (kws: number)=> void;
type FillPetrol = (type: string, liters: number) => void;
type RefillHandler<A extends Car> = A extends 'ICE' ? FillPetrol : A extends 'EV' ? ChargeEV : never;
const chargeTesla: RefillHandler<'EV'> = (power) => {
    // Implementation for charging electric cars (EV)
};
const refillToyota: RefillHandler<'ICE'> = (fuelType, amount) => {
    // Implementation for refilling internal combustion engine cars (ICE)
};
  • union type인 Car / 함수 타입 ChargeEV & FillPetrol
    • Car는 ICE, EV 값 중 하나를 가질 수 있는 union type
      • ICE는 내연 기관 차, EV는 전기차를 의미
    • ChargeEV 타입 ⇒ kws(전기량) 을 매개변수로 받아 아무것도 반환하지 않는 함수 (void)
    • FillPetrol 타입 ⇒ type(연료 종류), liters(리터 수) 매개변수, void
  • 조건부 타입 RefillHandler
    • A extends Car : Car 타입을 확장하는 제네릭 타입 A를 매개변수로 받음
      • A가 ICE일 경우 FillPetrol 타입의 함수를 반환
      • A가 EV일 경우 ChargeEV 타입의 함수를 반환
      • A가 ICE, EV가 모두 아닌 경우는 없으므로 never (컴파일 타임에서 안정성 보장 위함)
  • RefillHandler 타입에 ICE나 EV를 제네릭을 제공하면 해당 함수 타입이 결정됨
    • chargeTesla와 refillToyota에 각각 EV, ICE를 제네릭으로 제공하면 RefillHandler 타입은 각각 ChargeEV 타입과 FillPetrol 타입의 함수를 반환하게 됨
    • 즉, chargeTesla 함수는 kws(전기량)을 매개변수로 받는 ChargeEV 타입의 함수가 되고, refillToyota 함수는 type, liters를 매개변수로 받는 FillPetrol 타입의 함수가 됨
  • 즉, 조건부 타입과 union type을 활용하면 서로 다른 시그니처를 가진 함수를 처리할 수 있음

 

interface를 쓰는 게 더 좋은 경우

Declaration merging

  • Declaration merging : TypeScript에서 인터페이스가 갖는 특징
    • 동일한 인터페이스를 여러 번 정의할 수 있음
    • TS 컴파일러가 이러한 정의들을 자동으로 하나의 인터페이스 정의로 merge
interface Client { 
    name: string; 
}

interface Client {
    age: number;
}

const harry: Client = {
    name: 'Harry',
    age: 41
}
  • 두 번 선언된 Client
    • TS 컴파일러에 의해 하나로 merge됨
    • 그 결과 Client 인터페이스를 사용할 때 두 개의 프로퍼티가 있게 됨

출처 :&nbsp;https://blog.logrocket.com/types-vs-interfaces-typescript/

 

  • 반면, type aliases는 같은 방식으로 merge될 수 없음
  • 위와 같이 Client type을 한 번 이상 정의하면, 에러가 발생

 

declaration merging의 일반적인 사례

  • 서트파티 라이브러리의 타입 정의를 확장할 때

 

ex) 서드파티 라이브러리 사용중이고, 해당 라이브러리 특정 타입에 프로퍼티를 추가하려고 할 때

// node_modules/library/index.d.ts 에서 가져온 타입
interface ThirdPartyType {
    prop1: string;
}

// 우리의 프로젝트 파일 내에서 이 타입을 확장
interface ThirdPartyType {
    newProp: number;
}

// 이제 ThirdPartyType은 우리가 추가한 프로퍼티도 포함하게 됨
const item: ThirdPartyType = {
    prop1: "Hello",
    newProp: 42
};
  • ThirdPartyType 를 declaration merging 함으로써 ThirdPartyType 인터페이스에 newProp이라는 새로운 프로퍼티를 추가
  • 이렇게 하면, 해당 타입을 사용하는 모든 곳에서 새로운 프로퍼티를 사용할 수 있음
  • 이는 서드파티 라이브러리 타입에 커스텀 프로퍼티 추가하거나, 라이브러리가 업데이트되어 새로운 프로퍼티가 추가되었지만 타입 정의가 아직 업데이트되지 않은 경우 유용
  • 하지만 이는 기존 타입 정의를 변경하는 것이므로 타입 안정성 문제에 주의해야 함

 

Reference :

https://blog.logrocket.com/types-vs-interfaces-typescript/

https://joshua1988.github.io/ts/guide/type-alias.html#type-vs-interface