Backend/TypeScript

아이템13. type과 interface 차이점 알기

kimdozzi 2024. 12. 29. 14:16

타입스크립트에서 named type을 정의하는 방법은 두 가지가 있습니다. 대부분의 경우에는 타입을 사용해도 되고 인터페이스를 사용해도 됩니다. 그러나 타입과 인터페이스 사이에 존재하는 차이를 분명하게 알고, 같은 상황에서는 동일한 방ㅂ버으로 명명된 타입을 정의해 일관성을 유지해야 합니다. 

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

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

 

type과 interface의 비슷한 점

1. 인덱스 시그니처는 인터페이스와 타입에서 모두 사용할 수 있다.

type TDict = { [key:string]: string};
interface IDict {
    [key:string]: string;
}

 

 

2. 함수 타입은 인터페이스와 타입으로 정의 가능하다.

type TFunction = (x: string) => string;
interface IFunction {
    (x: string): string;
}

const toStrType: TFunction = x => '@' + x;
console.log(toStrType("hello"));

const toStrInterface: IFunction = x => '@' + x;
console.log(toStrInterface("hello"));

 

 

3. Generic 사용이 둘 다 가능하다. 

type TPair<T> = {
    x: T;
    y: T;
}
const numberTPair: TPair<number> = {
    x:10,
    y:20,
}
console.log(numberTPair.x + " " + numberTPair.y);


interface IPair<T> {
    x: T;
    y: T;
}
const numberIPair: IPair<number> = {
    x:20,
    y:30,
}
console.log(numberIPair.x + " " + numberIPair.y);

 

 

4. 인터페이스는 타입을 확장할 수 있고, 타입은 인터페이스를 확장할 수 있다.

여기서 주의할 점은 인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지는 못한다는 것입니다. 복잡한 타입을 확장하려면 type과 &를 사용해야 합니다. 

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

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

// 두 코드는 동일한 기능을 합니다. 
type TStateWithPop = IState & { population: number};

interface IStateWithPop extends TState {
    population: number;
}

 

 

5. 클래스를 구현(implements)할 때는, 타입과 인터페이스 둘 다 가능하다.

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

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

class StateT implements TState {
    name: string = "";
    capital: string = "";
}

class StateI implements IState {
    name: string = "";
    capital: string = "";
}

 

 

 

type과 interface의 다른 점

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

인터페이스는 타입을 확장할 수 있지만, 유니온은 할 수 없습니다. 그런데 유니온 타입을 확장하는 게 필요할 때가 있습니다. 아래와 같은 예시를 보면, 유니온 타입의 장점이 확연하게 드러납니다. 

type RequestState = 'idle' | 'loading' | 'success' | 'error';

type RequestData<T> = {
    state: RequestState;
    data?: T;
    error?: string;
}

interface User {
    id: number;
    name: string;
}

const userRequest: RequestData<User> = {
    state: 'success',
    data: {id:1, name: "John"}
};

const failedRequest: RequestData<User> = {
    state: 'error',
    error: "Failed to fetch user"
}

 

 

인터페이스로 구현해면 되지 않냐구요? 인터페이스로 구현하고, 타입을 사용했을 경우와 비교해보도록 하겠습니다. 한 눈에 봐도 복잡한 코드임을 알 수 있습니다. 이런 경우에는 타입스크립트의 type을 최대한 활용하는 것이 적절해 보입니다.

interface RequestStateMap {
    readonly IDLE: 'idle';
    readonly LOADING: 'loading';
    readonly SUCCESS: 'success';
    readonly ERROR: 'error';
}

const RequestState: RequestStateMap = {
    IDLE: 'idle',
    LOADING: 'loading',
    SUCCESS: 'success',
    ERROR: 'error'
} as const;

interface RequestData<T> {
    state: RequestStateMap[keyof RequestStateMap];
    data?: T;
    error?: string;
}

interface User {
    id: number;
    name: string;
}

const userRequest: RequestData<User> = {
    state: 'success',
    data: {id:1, name: "John"}
};

const failedRequest: RequestData<User> = {
    state: 'error',
    error: "Failed to fetch user"
}

 

 

2. type에는 없지만, interface에는 있는 선언 병합(declaration merging)

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

interface IState {
    population: number;
}

// 정상
const mergedInterface: IState = {
    name: "Kim",
    address: "Seoul",
    population: 500_000,
}

// Duplicate identifier 'TState'.
// type TState = {
//     name: string;
//     address: string;
// }

// type TState = {
//     population: number;
// }

 

 

 

 

 


참고 자료