Typescript

[Typescript] 이펙티브 타입스크립트 2장 아이템13

sian han 2023. 3. 5. 14:46

※ 아이템 13 타입과 인터페이스의 차이점 알기

 

▶ 타입스크립트에서의 타입 정의

▷ 1. 타입 사용

type TState = {
    name : string;
    capital: string;
}

 

▷ 2. 인터페이스 사용

interface IState {
    name: string;
    capital: string;
}

 

번외 : 클래스를 사용할 수도 있지만, 클래스는 값으로도 쓰일 수 있는 자바스크립트 런타임 개념이다.

아래 아이템 8의 내용을 참고해서 이해해보자.

 

49p ) 클래스가 타입으로 쓰일 때는 형태가 사용되는 반면, 값으로 쓰일 때는 생성자가 사용된다.

class Cylinder{
    radius=1;
    height=1;
}

function caculateVolume(shape: unknown){
    if(shape instanceof Cylinder){ //객체가 특정 클래스에 속하는지 확인
        shape //타입 : Cylinder
        shape.radius //타입 : number
    }
}

type 공간에 class 가 사용되면 형태만 쓰인다. type 공간에 적혀진 Cylinder 는 컴파일 될 때 사라진다.

Instanceof 뒤의 Cylinder 는 생성자를 의미한다. 클래스는 타입으로도 사용될 수 있고 값으로도 사용될 수 있다.

 

 

 

▶ 인터페이스 선언과 타입선언의 비슷한 점

 

▷ 추가속성과 함께 할당한다면 동일한 오류가 발생한다. 

const wyoming: TState = {
    name: 'wyoming',
    capital: 'Cheyenne',
    population: 500_000
};

오류메세지 :  개체리터럴은 알려진 속성만 지정할 수 있으며 TState (또는 IState) 형식에 population이 없다.

TState 예제이지만 IState 에서도 동일한 오류가 발생함

 

 

▷ 인덱스 시그니처를 사용할 수 있다.

//타입
type TDict = {[key: string]: string};

//인터페이스
interface IDict {
    [key: string]: string;
}

 

▷ 함수타입 정의

//타입
type TFn = (x: number) => string;
const toStrT: TFn = x => '' + x;

//인터페이스
interface IFn{
    (x: number): string;
}
const toStrI: IFn = x => '' + x;

 

▷ 제너릭이 가능하다

type TPair<T> = {
    first: T;
    second: T;
}

interface IPair<T>{
    first: T;
    second: T;
}

 

▷ 서로 확장이 가능하다

  • type 을 extends 한 interface
interface IStateWithPop extends TState{
    population: number;
}

 

  • interface 를 extends 한 type
type TStateWithPop = IState & {population: number;};

인터페이스는 유니온 타입같은 복잡한 타입을 확장하지는 못한다.

따라서 복잡한 타입을 확장하고 싶다면 `type`과 `&` 를 사용해야한다.

 

 

유니온타입

`I` 연산자로 표현되며, 여러개의 타입 중 하나의 타입을 가질 수 있도록 한다.

 

& 연산자

& 연산자는 TypeScript에서 intersection(교차) 타입을 나타내는 역할을 한다. 여러 개의 타입을 하나로 합쳐서 새로운 타입을 만들어 내는데 사용할 수 있다.

위 예시에서 & 연산자는 IState와 {population: number} 이라는 두 개의 타입을 합쳐서 새로운 타입인 TStateWithPop을 만들었다.

 

`type` 과 `&` 를 사용해 아래와 같은 복잡한 타입 정의가 가능하다.

type ComplexType = TypeA & TypeB | TypeC

 

 

 

▶ 인터페이스 선언과 타입선언의 차이점

 

유니온 타입은 있지만 유니온 인터페이스는 없다.

type AorB = 'a' | 'b';

인터페이스는 타입을 확장할 수 있지만, 유니온은 할 수 없다.

그렇지만 유니온 타입을 확장하는 것이 필요할 때가 있다.

 

▷ 유니온 타입 확장하기

1. name 속성이 string 타입이고 값으로

Input | Output 타입을 갖는 객체를 표현하는 VariableMap 인터페이스

type Input = {};
type Output = {};
interface VariableMap {
    [name: string]: Input | Output;
}

 

 

2. 유니온타입에 name 속성을 붙인 타입 NamedVariable 타입

type NamedVariable = (Input | Output) & {name: string};

 - 이 타입은 인터페이스로 표현할 수 없다.

 

 

type 키워드는 일반적으로 interface 보다 쓰임새가 많다.

  • type 키워드는 유니온이 될 수 있다.
  • 튜플과 배열 타입을 간결하게 표현할 수 있다.
type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];

 

인터페이스가 갖고있는 타입에 없는 기능들

  • 보강(augment) 이 가능하다.
    • 타입선언에는 사용자가 채워야하는 빈틈이 있을 수 있다. 선언병합을 지원하기 위해 반드시 인터페이스를 사용해야한다.
    • 프로퍼티가 추가되는 것을 원하지 않는다면 인터페이스 대신 타입을 사용하면 된다.
//선언병합 예제
interface IState {
    name: string;
    capital: string;
}

interface IState {
    population: number;
}

const wyoming:IState = {
    name: 'Wyoming',
    capital: 'Cheyenne',
    population:500_000
};

 

 

 

[상황별로 인터페이스와 타입중 어느것을 사용해야 할지 결정하기]

  • 복잡한 타입이다 => 타입 별칭 사용
  • 간단한 객체 타입이다 => 일관성과 보강의 관점에서 고려해본다.
  • API 에 대한 타입선언 => 인터페이스 사용
    • API 가 변경될 때 사용자가 인터페이스를 통해 새로운 필드를 병합할 수 있어 유용하기 때문이다.
    • 프로젝트 내부적으로 사용되는 타입에 선언병합이 발생하는 것은 잘못된 설계이다. (이럴때는 타입을 사용해야한다.)