[번역] [결론] typescript 자료형 선언은 type보다 interfcae를 사용해야하는 이유
들어가며
TypeScript에서 컴포넌트 Props나 객체 자료형 정의할 때, type와 interface 어느 쪽을 사용해야할까 한 번 고민했던 적이 있을 것 같입니다.
향간에서 "어느 쪽도 상관없다", "팀에서 통일하면 OK" 의견도 있는데요. 하지만, 저는 명확한 이유를 가지고 "기본적으로 interface를 사용할 것"이라 주장합니다.
이 글에서는 저의 실체험에서 조우한 React Props의 심각한 성능 문제를 예로 들며, 왜 interface가 우수한가, 그리고 type는 어떤 장면에서 사용할가 해설합니다.
type alias를 사용하고 싶어지는 매력과 그 뒷면에 숨겨져 있는 함정
먼저 왜 많은 개발자가 type를 고를까요 그것은 좋은 개발 체험때문입니다.
type 정의한 자료형은 VSCode 등에서 에디터가 호버하면, 최종적으로 정의된 자료형 정보가 인라인으로 게시됩니다.
예를 들어 React의 Props를 정의해봅시다.
interface 자료형 정의

export interface IconButtonProps extends HTMLAttributes<HTMLButtonElement>{
icon: React.ReactNode;
}
export const IconButton = (props: IconButtonProps) => {
// ...
}
interface를 사용할 때는 이름만 게시됩니다.
type alias 자료형 정의
export type IconButtonProps = HTMLAttributes<HTMLButtonElement> & {
icon: React.ReactNode;
};
export const IconButton = (props: IconButtonProps) => {
// ...
};

선언한 내용이 게시됩니다.
이런식으로 type은 경우 합성된 자료형이라 하더라도, 최종적인 구조가 한 눈에 들어옵니다. 저도 이 "알기 편함"를 이유로, 업무에서는 늘 type를 사용 했습니다. 하지만, 이것이 후에 큰 문제를 일으킵니다.
type alias가 일어키는 성능 지옥
저희 팀이 개발한 제품이 성장하고, 코드베이스가 커진 어떤 날이었습니다. 뭘하더라도, 에디터의 자료형 체크가 꽤나 느려졌습니다. 문자를 입력할 때마다 CPU가 울고, 한 편 TypeScript의 자료형 정보가 보이지 않았죠.
더욱 심각한 것은 CI/CD에서 빌드 시간입니다. 지금까지 1분정도로 완료되었던 자료형 체크가, 돌연 30분이상 걸렸습니다.
다행스럽게도 이 현상은 아직 머지 전 특정 브런치에서만 발생했었기에, 대상 브런치의 작성을 일시정지하고, 다른 브런치에서 개발을 이어가기로 했습니다.
하지만, 이 브런치의 변경은 중요한 변경이 있어서, 긴급 머지하고 싶은 것이었죠.
팀 멤버가 1주일 이상, 설정을 다시 확인하거나, 라이브러리를 다운로드 해보거나, 이것 저것 테스트했봤습니다. 하지만 여전히 원인은 알 수 없었습니다.
문제가 발생한 커밋은 공통 폼 컴포넌트를 사용한 폼 필드 추가입니다.
이 공통 폼 컴포넌트는 검증 라이브러리 스키마에서 자료형을 추론하는 구조를 갖아, 비교적 복잡한 구조입니다.
하지만 더 대규모 혹은 복잡한 시스템을 갖고 있는 TanStack 같은 라이브러리에는 같은 문제가 일어나지 않았기에, "자료형이 복잡해서 늦다"라는 단순한 이야기가 아닐지도 모른다는 생각을 했습니다.
모든 방법을 사용해본 저는 지푸라기라도 잡는다는 심정으로, 어떤 가설을 세웠습니다. "혹시 type이 원인인걸까?"
시험삼아 프로젝트 전체의 자료형 정의를 기계적으로 type에서 interface로 일괄 변환하는 정규표현을 작성하고 실행했습니다.
그러자 놀랍게도 그렇게 저희를 괴롭혔던 에디터의 지연은 없어지고, 빌드도 1분으로 돌아왔습니다. 범인은 저희가 편리하다 믿고 사용했던 type이었습니다.
혹시 비슷한 문제로 곤란할 경우
여기에 VSCode의 일괄 변경에 사용할 정규표현을 준비해 뒀습니다!
Ctrl + Shift + F 혹은 Cmd + Shift + F 검색으로 패널을 열고, 정규표현을 유효화 하면 이하의 설정을 검색 치환 가능합니다.
Find: (\s)(?:export\s+|declare\s+)type\s+([A-Za-z_]*(?:<[>]>)?)\s=\s(.+?)\s&\s{\s\r?
([\s\S]?)^\1}\s;?
Replace: $1interface $2 extends $3 { $4$1}
복수 교차하고 있다면 이것을 반복해 사용해주세요.
Find: (\s*interface\b[{]\bextends\b[^{}]?)\s&\s
Replace: $1,
왜 interface는 성능이 우수한가?
이유는 단순한데, type와 interface 자료형 계산 타이밍이 다르게 때문입니다.
type: 즉각평가(Eager Evaluation)
type은 자료형 앨리아스입니다. 즉, 기존 자료형에 다른 이름을 붙이는 기능이죠. TypeScript 컴파일러는 type를 보면, 그 곳에서 자료형을 재귀적으로 해결/전개해서 구체적인 하나의 자료형을 보여줍니다.
앞의 예시의 호버할 때 구체적인 자료형이 보인 이유는, 바로 이 "즉각평가"했다는 증거죠.
교차 자료형(&)이 몇 겹이나 겹친 복잡한 자료형 정의에는, 이 계산 비용이 지수 함수적으로 증폭되고, 자료형 체크 전체 성능을 현저히 저하시킵니다.
interface: 지연평가(Lazy Evaluation)
한 편, interface는 새로운 이름을 붙이는 객체 자료형을 선언하는 것입니다. 컴파일러는 interface를 하나의 이름(심볼)로 다룹니다.
interface로 정의된 자료형은 실제 그 프로퍼티가 참조되는 등, 자료형 정보가 필요로 해질때 까지, 내부 구조의 완전한 계산을 지연합니다. 이것이 지연 평가입니다.
interface에 호버하면 이름만 보이는 것도 이 때문입니다.
이 지연 평가 구조에 의해서, interface는 어떤 복잡한 계승이어도, 프로젝트가 꽤나 거대해도, 성능에 영향을 최소한으로 미칩니다. 대규모 OSS 라이브러리 코드를 봤을 때, 대부분이 interface 자료형인 이유는, 이 스케빌리티가 이유입니다.
interface가 성능이 우수하다는 사실은 TypeScript 공식 문서에도 명기되어 있습니다.
Using interfaces with extends can often be more performant for the compiler than type aliases with intersections 번역: interface를 extends 확장하는 편이, 자료형 앨리어스를 intersection으로 합치는 것 보다 컴파일러에서 성능이 우수할 때가 많습니다.
type은 언제 사용할까?
그럼 type을 완전히 사용하지 않아야 할까요? 아뇨 그렇지 않습니다. interface로는 표현할 수 없는 자료형을 정의할 때, type를 사용해야합니다.
구체적으로는 이하의 경우겠죠.
- Union 자료형를 정의할 때
type Status = "success" | "error" | "loading";
- 튜플 자료형 정의할 때
type UserTuple = [name: string, age: number];
- Mapped Types 같이 복잡한 조작할 때
type ReadonlyUser = { readonly [K in keyof User]: User[K] }
- 원시 자료형에 다른 이름을 붙일 떄
객체의 형상을 정의하는 것 이외로 상기한 케이스에서 type는 꽤나 발군입니다.
덤: OSS에서 배우는 interface 가독성을 높이는 방법
대규모 OSS 라이브러리에는 interface 사용하면서도 가독성을 높이기 위해서 몇 가지 작업이 있었습니다.
JSDoc 주석 활용

interface PageProps<AppRoute extends AppRoutes>
Props for Next.js App Router page components
@exmaple
exprot default function Page(props: PageProps<"/blog/[slug]">){
const {slug} = await props.params
return <div>Blog post: {slug}</div>
}
Next.js 자료형 정의에는 interface에 JSDoc주석을 충실히 함으로써 호버할 때 구체적인 설명을 볼 수 있습니다.
또한 interface 이름을 구체적으로 혹은 설명적으로 명명해서 코드를 읽기만해도 역할을 이해할 수 있도록 작업되어 있습니다.
유틸리티 타입 활용

Hono 자료형 정의는 유저에게 공개 직전에 Simplify
이는 이하와 같이 구현되어있는데, 계승에 포함되는 자료형을 전개하기 위한 자료형 유틸리티입니다.
type Simplify
말단에 한 번 이용하거나, 포함하는 프로퍼티 수가 적은 경우에 유효합니다.
이런 작업을 함으로, interface의 이점을 살려가면서, 개발체험 향상을 노릴 수 있습니다.
결론 기본은 interface, 할 수 없을 때만 type
"어느 쪽으로 통일한 편이 좋다" 주장을 종종 보지만, 저는 이것이 틀렸다 생각합니다. type와 interface는 애초에 역할이 다르고, 통일할 대상이 아닙니다.
저희가 따를 규칙은 간단합니다.
객체 형상을 정의할 때는 먼저 interface를 사용. interface로 표현할 수 없는 자료형(Union 타입 같이)을 정의할 때 필요하면 type 사용.
이 지침을 따름으로, TypeScript의 강력한 타입 시스템의 혜택을 받으면서, 애플리케이션 성능과 스케빌리티를 유지할 수 있게 되었습니다.
type 호버할 때 알기 쉽게하는 것이 매력이지만, 이것이 프로젝트를 침식시키는 성능 문제를 일으킬 가능성이 있으니 반드시 기억해 주십시오.
이 글이 type vs interface 논쟁 하나의 결론을 넘어, 당신의 개발체험을 보다 더 좋게하는데 도움이 되면 좋게습니다.