TypeScript type vs interface 객체 타입 설계 기준

··설명 약 8분·작성: 해비·블로그 소개

TypeScript 객체 타입을 설계할 때 type과 interface는 어떻게 선택하나요?

type과 interface는 모두 객체의 필드 구조를 표현합니다. union처럼 객체 밖의 타입 조합이 필요하면 type을, 선언 병합과 공개 구조 확장이 필요하면 interface를 검토합니다.

적용 조건·범위: 객체가 같아 보인다고 확장 방식의 충돌 처리까지 같은 것은 아닙니다. interface 확장의 속성 충돌은 선언에서 드러나고, intersection은 양쪽 조건을 동시에 요구해 속성 타입이 never가 될 수 있습니다. 충돌을 해결할 때 두 선언을 기계적으로 서로 바꾸지 않습니다.

본문 근거: TypeScript type과 interface 선택 기준

직접 확인: 오류에서 수정까지

TypeScript type vs interface 객체 타입 설계 기준 · 검증 결과 보기

검증 결과 보기: 고정된 오류 예제와 수정 예제의 실제 tsc 진단을 비교하세요. 브라우저 실시간 컴파일러는 아닙니다.

예제 크게 보기 (새 탭)

단독 검사는 tsconfig.json이 없는 새 폴더에서 example.ts 한 파일씩 교체하며 진행합니다. TypeScript가 없다면 설치 글에서 Node.js/npm·TypeScript 준비를 확인하세요. 타입 검사 성공은 코드 실행 성공과 구분합니다.

독자 질문: Task 모델을 type과 interface 중 무엇으로 선언하면 좋을까요?

얻을 결과: 객체 shape는 둘 다 표현하고 union은 type으로 정의해 일관된 선택 기준을 세웁니다.

오류 코드를 별도 example.ts에 복사합니다.

interface Task { id: string; title: string }
const task: Task = { id: '1', title: '공부', done: false };

예상 타입 오류 (TS2353): 객체 리터럴의 done은 Task에 없는 속성입니다. 해당 속성을 제거하거나 실제 도메인 요구에 맞춰 Task에 정의합니다.

위 코드를 다음 수정 예제로 교체합니다.

type Task = { id: string; title: string };
const task: Task = { id: '1', title: '공부' };
type TaskFilter = 'all' | 'done';
npx tsc --strict --noEmit example.ts

기존 최소 예제 확인 기록: TypeScript 7.0.2의 별도 최소 예제에서 오류 TS2353, 수정 후 exit 0 확인. TaskFlow 전체 체크포인트·브라우저 실행은 미확인.

다음 학습: 06회차에서 union·null·unknown을 조건으로 좁힙니다.

type alias

type Task = {
  id: string;
  title: string;
  completed: boolean;
};

type은 객체뿐 아니라 다양한 타입 표현에 사용할 수 있습니다.

type TaskFilter =
  | 'all'
  | 'active'
  | 'completed';

union, tuple, conditional type 등 폭넓게 사용할 수 있습니다.

interface

interface Task {
  id: string;
  title: string;
  completed: boolean;
}

객체 구조를 표현할 때 읽기 쉽습니다.

확장

interface:

interface BaseTask {
  id: string;
  title: string;
}


interface Task
  extends BaseTask {
  completed: boolean;
}

type:

type BaseTask = {
  id: string;
  title: string;
};


type Task =
  BaseTask & {
    completed: boolean;
  };

둘 다 확장할 수 있습니다.

union

interface 자체를 union으로 선언할 수는 없습니다.

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

이런 경우 type이 자연스럽습니다.

declaration merging

interface는 같은 이름으로 여러 선언이 있을 때 병합될 수 있습니다.

interface Window {
  appVersion: string;
}


interface Window {
  userId: string;
}

라이브러리 타입 확장이나 전역 declaration augmentation에서 유용할 수 있습니다.

일반 애플리케이션 도메인 모델에서는 의도치 않은 병합이 필요하지 않은 경우도 많습니다.

객체 타입 선택 기준

객체 타입뿐 아니라 union·Utility Type까지 같은 문법으로 관리하려면 type alias 중심으로 작성할 수 있습니다.

이유는 다음과 같습니다.

  • TaskFilter 같은 union과 함께 사용
  • Utility Type, mapped type과 연결
  • 한 문서에서 타입 표현 방식을 통일
  • 객체 외 타입도 같은 문법으로 관리

하지만 외부 라이브러리 declaration 확장이나 공개 interface가 더 자연스러운 상황에서는 interface를 사용할 수 있습니다.

잘못된 논쟁

다음처럼 절대 규칙으로 외울 필요는 없습니다.

객체면 interface
나머지는 type

또는:

무조건 type만 사용

프로젝트에서 실제로 필요한 기능을 확인합니다.

독립 예제

type UserRole =
  | 'admin'
  | 'member';


type User = {
  id: string;
  name: string;
  role: UserRole;
};


interface ApiError {
  message: string;
  status: number;
}

한 프로젝트에서 type과 interface를 함께 사용할 수도 있습니다.

중요한 것은 역할이 명확한가입니다.

객체가 같아 보인다고 확장 방식의 충돌 처리까지 같은 것은 아닙니다. interface 확장의 속성 충돌은 선언에서 드러나고, intersection은 양쪽 조건을 동시에 요구해 속성 타입이 never가 될 수 있습니다. 충돌을 해결할 때 두 선언을 기계적으로 서로 바꾸지 않습니다.

원리 확인: TypeScript type과 interface 선택 기준 (2026-10-12). 아래 기대 결과와 기존 실행 기록은 구분해서 읽습니다.

학습 완료 체크리스트

같은 Task 객체를 두 방식으로 검사한 뒤 TaskFilter union에는 무엇을 사용할지 정해 보세요. 이름을 바꾸는 취향과 선언 병합·union 같은 기능 요구를 구분해 선택 이유를 말할 수 있어야 합니다.


작은 변형으로 확인하기

수정 예제에서 type Task를 같은 필드의 interface Task로 바꿔도 객체 할당은 통과합니다. TaskFilter union까지 interface 한 선언으로 바꿀 수 있는지 비교하세요.

선택 실습: 할 일 앱에 적용하기

TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 JavaScript 코드를 TypeScript로 확장합니다.

TaskFlow의 타입 파일을 목적별로 정리합니다.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

TypeScript type vs interface 객체 타입 설계 기준 · 단계별 TaskFlow

타입을 적용한 TaskFlow에서 작업을 추가·완료 전환·삭제하세요. 타입만 바뀐 단계는 이전과 화면이 같습니다.

예제 크게 보기 (새 탭)

시작·완료 파일과 적용 순서에서 이번 변경 파일의 전체 내용과 적용 순서를 확인합니다.

시작본과 이번 변경

TypeScript 기본 타입: 객체와 함수까지 strict로 익히기의 완료 체크포인트를 기준으로 이어갑니다. 변경하지 않는 파일은 그 시작 상태를 유지합니다.

src/types/task.ts와 src/types/settings.ts를 분리합니다. 도메인 타입은 type으로 유지하고 외부 확장이 필요한 interface와 목적을 구분합니다. 기존 호출부의 import 경로와 필드가 유지되는지 검사합니다.

이전 체크포인트

typescript-step-04-basic-types

이번 목표

TaskFlow의 타입 파일을 목적별로 정리합니다.

전체 파일·코드 보기

src/types/task.ts

export type Task = {
  id: string;
  title: string;
  completed: boolean;
};


export type TaskFilter =
  | 'all'
  | 'active'
  | 'completed';

src/types/settings.ts

export type TaskSettings = {
  showCompleted: boolean;
  compact: boolean;
};

이 TaskFlow 과정의 코어 타입은 type alias로 통일합니다. TaskFilter 같은 union과 객체 타입을 같은 문법으로 관리하기 위한 과정의 선택이며, 모든 프로젝트에 같은 방식을 요구하는 규칙은 아닙니다.

interface 비교 예제

외부 API 오류 객체를 interface로 작성해도 됩니다.

export interface ApiError {
  message: string;
  status: number;
}

이번 프로젝트에서 interface가 꼭 필요한 것은 아니므로 Task 모델을 interface로 바꾸기 위한 리팩터링은 하지 않습니다.

검증

npx tsc --noEmit

중간부터 시작하는 전체 파일과 적용 순서

이번 글 시작 ZIP · 이번 글 완료 ZIP

시작 ZIP은 직전 프로젝트 단계 typescript-step-04-basic-types의 실제 완료 파일입니다. 프로젝트 순서를 이어가는 기준이며 사이트의 기술 읽기 순서와는 구분합니다.

  1. 시작 ZIP을 새 폴더에 풉니다. 이 폴더가 작업 루트이며 기존 체크포인트 파일을 별도로 찾을 필요가 없습니다.
  2. TaskFilter·TaskSettings·ApiError 계약을 각각 파일로 추가합니다. 기존 core 함수와 화면은 유지합니다.
  3. 아래 변경 파일을 같은 상대 경로에 추가하거나 전체 교체합니다. 표시하지 않은 파일과 images는 시작본 그대로 유지합니다. package-lock.json은 완료 ZIP에 있는 파일을 함께 적용합니다.
  4. 작업 루트에서 명령을 실행하고 예상 결과를 확인합니다. 완료 ZIP은 직접 적용한 결과를 비교하는 기준입니다.

추가·교체 파일

  • CHANGES.md — 전체 교체
  • README.md — 전체 교체
  • src/types/task.ts — 전체 교체
  • src/types/settings.ts — 추가
  • src/types/api-error.ts — 추가

실행과 확인

npm ci
npm run dev
npm run typecheck
npm test
npm run build

개발 서버는 실행 상태로 두고 다른 터미널에서 검사 명령을 실행하세요. Node.js 24.18.0에서 검증했습니다. JavaScript는 http://127.0.0.1:4173, TypeScript는 Vite 실행 출력의 주소로 접속합니다.

예상 결과: 객체의 필수 필드와 필터 허용값을 타입 검사합니다.

기존 ZIP 검증 기록: TypeScript strict 검사·긍정/부정 타입 계약·런타임 검사·Vite 빌드 통과. 시작 ZIP에 이 글의 변경 파일만 적용한 결과와 완료 ZIP의 모든 파일 내용이 일치했습니다. 브라우저 화면 배치·실제 기기·외부 CDN 가용성은 이 검사에 포함하지 않았습니다.

이 절의 전체 파일은 ZIP과 일치합니다. 위쪽 원문의 짧은 예제나 생략 표시가 있는 부분 코드는 개념·변경 위치 설명용입니다. 서버 저장과 인증은 제공하지 않으며 JSON fixture는 로컬 읽기 실습용입니다.

복사할 변경 파일 전체 코드

src/types/task.ts — 전체 파일

export type Task = {
  id: string;
  title: string;
  completed: boolean;
};


export type TaskFilter =
  | 'all'
  | 'active'
  | 'completed';

src/types/settings.ts — 전체 파일

export type TaskSettings = {
  showCompleted: boolean;
  compact: boolean;
};

src/types/api-error.ts — 전체 파일

export interface ApiError {
  message: string;
  status: number;
}

완료 체크포인트

typescript-step-05-object-types

다음 학습

다음 글에서는 union, null, unknown 값을 안전하게 좁히는 narrowing을 배웁니다.

다음 글: TypeScript 타입 좁히기: union null unknown 안전 검증

글 소개·학습 준비·전체 목차
TypeScript type vs interface 객체 타입 설계 기준

TypeScript · 5 / 22 · 필수 학습 · type vs interface

객체 모델은 type과 interface 중 무엇으로 선언할까요? 둘 다 객체 구조를 표현할 수 있습니다. union 등 객체 밖 표현이 필요하면 type을, 선언 병합이나 공개 구조의 확장이 필요하면 interface를 검토하고 기존 프로젝트 방식과 맞춥니다. 같은 필드의 두 선언을 비교해 선택 이유를 설명합니다.

이번 글에서 배우는 내용

  • type alias
  • interface
  • 객체 shape 표현
  • extends와 intersection
  • union에서 type이 자연스러운 이유
  • declaration merging
  • 객체 타입 선택 기준

단독 학습 선수 지식

객체 타입·union과 함수 매개변수 타입을 알면 됩니다. 이전 TaskFlow 파일 없이 직접 확인 예제로 학습할 수 있습니다.

목차

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 22개 · 필수 21개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기