명명된 타입을 선언할 때 개발자들이 가장 많이 하는 고민이지 않을까 싶다.
어떨 때 타입을 사용하고, 어떨 때 인터페이스를 사용해야 할까?
오늘 그 고민에 대한 정리를 책을 읽고 깨달은 점에 대하여 정리할 수 있었다.
type Tstate = {
name: string;
capital: string;
}
interface IState {
name: string;
capital: string;
}
이 두가지 차이를 명확하게 이해하고 어떤 상황에서 어떤 것을 써야 하는지 알아보도록 하자.
const wyoming: TState = {
name: 'Wyoming',
capital: 'Cheyenne',
population:5 50000
};
// ..형식은 'TState' 형식에 할당할 수 없습니다. 개체 리터럴은 알려진 속성만 지정할 수 있으며, TState 형식에는 population이 없습니다.
type TDict = { [key: string]: string};
interface IDict {
[key: string]: string;
}
type TFn = (x: number) => string;
interface IFn {
(x: number) :string;
}
이렇게 간단한 경우에는 타입 별칭이 더 나을수도 있겠지만, 함수 타입에 추가적인 속성이 있다면 타입, 인터페이스 둘 중 아무거나 선택해도 된다.
type TFnWithProperties = {
(x: number): number;
prop: string;
}
interface IFnWithProperties {
(x: number): number;
prop: string;
}
타입 별칭과 인터페이스는 모두 제너릭이 가능하다.
type TPair<T> = {
first: T;
second: T;
}
interface IPair<T> {
first: T;
second: T;
}
class StateT implements TState {
name: string = '';
capital: string = '';
}
class StateI implements IState {
name: string = '';
capital: string = '';
}
보강 기능이 가능하다.
interface IState {
name: string;
capital: string;
}
interface IState {
population: number;
}
const wyoming: IState = {
name: 'Wyoming',
capital: 'Cheyenne',
population: 500_000
};
이렇게 속성을 확장하는 것은 선언 병합이라고 한다.
타입 선언 파일을 작성하게 될 때에는 선언 병합을 지원하기 위해 반드시 인터페이스를 사용하는데, 이는 나중에 더 자세히 배울 것이다.
Array 인터페이스는 lib.es5.d.ts
에 정의되어 있는데, 기본적으로는 해당 파일에 선언된 인터페이스가 사용된다. 만약에 tsconfig.json
의 lib 목록에 ES2015를 추가하면 타입스크립트는 lib.es2015.d.ts
에 선언된 인터페이스를 이용하는데, 이때 병합을 통하여 Array 인터페이스에 추가되는 것이다.
이렇게 각 선언이 병합되어 전체 메서드를 가진 Array타입을 얻게 되는 것이다.
이렇게 병합을 하고자 하면 인터페이스로 타입 선언 파일을 작성한다.
백엔드와 소통하며 API는 언제든지 바뀔 수 있다. API에 대한 타입 선언을 인터페이스로 생성하는 것이 좋다. 새로운 필드를 개발자가 병합하여 개발하게 될 때 유용하게 대처할 수 있기 때문이다. 그러나 api를 제외하고 프로젝트 내부에 인터페이스를 무분별하게 사용하여 타입에 선언 병합이 일어나는 것은 좋지 못하므로 주의해야 한다.
type Input = {/* ... */};
type Output = {/* ... */};
interface VariableMap {
[name: string]: Input | Output;
}
Input
과 Output
은 별도의 타입이고, 이 둘을 하나의 변수명으로 매핑하기 위하여 유니온 타입을 이용하여 새로운 인터페이스를 만들 수 있다.
혹은 다음과 같이 표현할 수 있다.
type NamedVariable = (Input | Output) & {name: string}
type 키워드는 유니온이 될 수도 있고, 매핑된 타입 또는 조건부 타입 같은 고급 기능에 활용될 수 있다.
type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];
인터페이스를 이용하여 튜플과 비슷하게 구현할 수 있다.
interface Tuple {
0: number;
1: number;
length: 2;
}
그러나 이렇게 비슷하게 구현해도, 튜플에서 사용할 수 있는 concat
과 같은 메서드를 사용할 수 없다. 그래서 튜플은 type로 구현하는 것이 낫다.