인터페이스와 타입 둘 다 타입스크립트에서 명명된 타입을 정의하는 방법이다.
type TCountry= {
name: string;
capital: string;
}
interface ICountry {
name: string;
capital: string;
}
// 예제에서는 인터페이스와 타입명에 접두사 I와 T를 사용하여 구분하였으나 현재는 지양해야할 스타일
대부분의 경우 타입을 사용해도 되고 인터페이스를 사용해도 된다. 그러나 타입과 인터페이스 사이에 존재하는 차이를 분명히 알고, 같은 상황에서는 동일한 방법으로 명명된 타입을 정의해 일관성을 유지해야 하는 것이 좋다. 그러려면 하나의 타입에 대해 두 가지 방법을 모두 사용해서 정의할 줄 알아야 한다.
명명된 타입은 인터페이스로 정의하든 타입으로 정의하든 상태에는 차이가 없다.
추가 속성과 함께 할당한다면 동일한 오류가 발생한다
const korea: Country = {
name: ‘Korea’,
Capital: ‘Seoul’
population: '50M' // 오류 발생 (지정된 속성이 아님)
}
인덱스 시그니처는 인터페이스와 타입에서 모두 사용 가능하다.
type TDict = { [key:string]: string};
interface IDict {
[key:string]: string;
}
함수 타입도 인터페이스나 타입 둘 다로 정의 가능하다.
type TFn = (x: number) => string;
interface IFn {
(X: number): string;
}
타입 별칭과 인터페이스는 모두 제너릭이 사용 가능하다.
type TPair<T> = {
first: T;
second: T;
}
interface IPair<T> {
first: T;
second: T;
}
인터페이스는 타입을 확장할 수 있으며, 타입은 인터페이스를 확장할 수 있다.
interface ICountryWithPop extends TCountry {
population: number;
}
type TCountryWithPop = ICountry & {population: number;};
ICountryWithPop과 TCountryWithPop은 동일하다. 여기서 주의할 점은 인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지는 못한다. 복잡한 타입을 확장하고 싶다면 타입과 &을 사용해야 한다.
한편 클래스를 구현(implements)할 때는 타입과 인터페이스 둘 다 사용 가능하다.
class CountryT implements TCountry {
name: string = ‘’;
capital: string = ‘’;
}
class CountryI implements ICountry {
name: string = ‘’;
capital: string = ‘’;
}
유니온 타입은 있지만 유니온 인터페이스라는 개념은 없다.
인터페이스와 달리 타입 앨리어스는 원시값, 유니온 타입, 튜플 등도 타입으로 지정할 수 있다.
// 문자열 리터럴로 타입 지정
type Str = 'Lee';
// 유니온 타입으로 타입 지정
type Union = string | null;
// 문자열 유니온 타입으로 타입 지정
type Name = 'Lee' | 'Kim';
// 숫자 리터럴 유니온 타입으로 타입 지정
type Num = 1 | 2 | 3 | 4 | 5;
// 객체 리터럴 유니온 타입으로 타입 지정
type Obj = {a: 1} | {b: 2};
// 함수 유니온 타입으로 타입 지정
type Func = (() => string) | (() => void);
// 인터페이스 유니온 타입으로 타입 지정
type Shape = Square | Rectangle | Circle;
// 튜플로 타입 지정
type Tuple = [string, boolean];
const t: Tuple = ['', '']; // Erro
인터페이스는 타입을 확장 가능하지만 유니온 타입은 확장하지 못한다 그런데 유니온 타입을 확장할 필요가 있을 때가 있다. 이때는 별도의 타입을 하나의 변수명으로 매핑하는 variableMap 인터페이스를 사용한다.
type Input = { ... };
type Output = { ... };
interface VariableMap {
[name: string]: Input | Output;
}
또한 유니온 타입에 name 속성을 붙인 타입을 만들 수도 있다.
type NamedVariable = (Input | Output) & { name: string}
이런 타입은 인터페이스로 표현할 수 없다.
튜플과 배열 타입도 타입 앨리어스를 이용해 더 간결하게 표현 가능하다.
type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];
인터페이스로도 튜플과 비슷하게 구현할 수 있기는 하다.
interface Tuple {
0: number;
1: number;
length: 2;
}
const t: Tuple = [10, 20];
그러나 인터페이스로 튜플과 비슷하게 구현하면 튜플에서 사용할 수 있는 concat과 같은 메서드들을 사용할 수 없다. 그러므로 튜플은 type 키워드로 구현하는 것이 낫다.
반면 인터페이스에는 타입에 없는 몇 가지 기능이 있다. 그 중 하나는 보강(augment)이 가능하다는 것이다.
Interface IState =.
name: string;
capital: string;
}
Interface IState {
population: number;
}
const wyoming: Istate = {
name: ‘Wyoming’,
capital: ‘Cheyenne’,
population: 500_000
};
위와 같이 속성을 확장하는 것을 선언 병합 (declaration merging) 이라고 한다.
객체의 확장에 있어서 인터페이스가 더 유리하다. 확장이 필요한지에 따라 타입을 쓸지 인터페이스를 쓸 지 팀내에서 공통적으로 정하여 사용하는 것이 좋다.
[책] 이펙티브 타입스크립트 http://www.yes24.com/Product/Goods/102124327
https://poiemaweb.com/typescript-alias