TypeScript에서 인터페이스란 무엇인가? 그리고 모듈로 바닐라 프로젝트 파일을 나누는 방법

TypeScript를 배우다 보면 가장 먼저 자주 만나게 되는 개념 중 하나가 interface입니다. 처음에는 문법처럼 보이지만, 실제로는 데이터의 모양을 약속하는 도구라고 이해하면 훨씬 쉽습니다.
그리고 프로젝트가 조금만 커져도 파일 하나에 모든 코드를 몰아넣는 방식은 금방 불편해집니다. 이럴 때 필요한 것이 모듈 방식으로 파일을 나누는 개발 방법입니다. 특히 바닐라 JavaScript 또는 바닐라 TypeScript 프로젝트에서도 모듈을 잘 사용하면 코드가 훨씬 읽기 쉽고 관리하기 쉬워집니다.
이번 글에서는 다음 두 가지를 함께 정리해보겠습니다.
- TypeScript에서 인터페이스가 무엇인지
- 모듈 방식으로 바닐라 프로젝트 파일을 나누는 방법
1. TypeScript에서 인터페이스란?
인터페이스는 객체의 구조를 정의하는 문법입니다.
예를 들어 Todo 데이터를 다룬다고 해봅시다.
interface Todo { id: number; title: string; done: boolean; }
위 코드는 "Todo라는 이름의 데이터는 id, title, done이라는 속성을 가져야 한다"는 뜻입니다.
즉, 인터페이스는 값을 만드는 코드가 아니라 값의 형태를 설명하는 설계도에 가깝습니다.
인터페이스가 필요한 이유
JavaScript에서는 아래처럼 객체를 바로 사용할 수 있습니다.
const todo = { id: 1, title: "타입스크립트 공부하기", done: false, };
하지만 프로젝트가 커지면 이런 고민이 생깁니다.
id는 문자열인가 숫자인가?done은 꼭 있어야 하나?- API 응답 데이터 구조가 항상 같은가?
- 여러 파일에서 같은 객체 구조를 반복해서 설명해야 하나?
TypeScript의 인터페이스를 사용하면 이런 문제를 줄일 수 있습니다.
interface Todo { id: number; title: string; done: boolean; } const todo: Todo = { id: 1, title: "타입스크립트 공부하기", done: false, };
이제 TypeScript는 todo가 Todo 구조를 지키는지 검사해줍니다.
2. 인터페이스를 사용하면 좋은 점
2-1. 데이터 구조를 한눈에 알 수 있다
interface User { username: string; nickname: string; email: string; }
이 코드만 봐도 User 객체가 어떤 데이터를 가지는지 바로 알 수 있습니다.
2-2. 오타나 잘못된 타입을 빨리 발견할 수 있다
interface User { username: string; nickname: string; } const user: User = { username: "kim", nickName: "K", // 오타 };
위 코드는 nickname이어야 하는데 nickName이라고 써서 오류가 발생합니다. 이런 실수를 미리 잡을 수 있다는 것이 큰 장점입니다.
2-3. API 응답 타입을 명확하게 만들 수 있다
예를 들어 Todo 목록 API가 아래처럼 응답한다고 해보겠습니다.
{ "success": true, "data": [ { "id": 1, "title": "공부하기", "done": false } ] }
이럴 때 TypeScript에서는 다음과 같이 정의할 수 있습니다.
interface Todo { id: number; title: string; done: boolean; } interface TodoListResponse { success: boolean; data: Todo[]; }
이렇게 해두면 response.json()으로 받은 데이터가 어떤 구조인지 더 분명하게 관리할 수 있습니다.
3. type과 interface는 뭐가 다른가?
초보자가 가장 많이 헷갈리는 부분 중 하나입니다.
간단하게 말하면:
interface: 객체 구조를 설명할 때 자주 사용type: 객체뿐 아니라 유니온, 튜플, 함수 타입 등 더 넓은 범위 표현 가능
예를 들면:
interface User { name: string; } type Status = "idle" | "loading" | "success" | "error";
실무에서는 둘 다 많이 씁니다. 하지만 API 응답, Todo 객체, 사용자 정보 같은 구조를 정의할 때는 interface부터 익히는 것이 이해하기 쉽습니다.
4. 바닐라 프로젝트에서 파일을 나눠야 하는 이유
처음에는 main.ts 하나에 모든 코드를 작성해도 괜찮습니다. 하지만 기능이 늘어나면 금방 이런 상태가 됩니다.
- 로그인 코드
- 회원가입 코드
- Todo 등록 코드
- Todo 목록 조회 코드
- DOM 조작 코드
- 타입 정의 코드
이 모든 코드가 한 파일에 있으면:
- 찾기 어렵고
- 수정하기 어렵고
- 재사용하기 어렵고
- 협업하기 어려워집니다
그래서 기능별로 파일을 나누는 모듈 방식이 필요합니다.
5. 모듈이란?
모듈은 파일 단위로 코드를 분리하고, 필요한 것만 export/import 해서 사용하는 방식입니다.
예를 들어 하나의 파일에서 함수를 밖으로 내보내려면 export를 사용합니다.
export function add(a: number, b: number): number { return a + b; }
다른 파일에서 사용하려면 import를 씁니다.
import { add } from "./math"; console.log(add(1, 2));
이 구조가 바로 모듈 방식입니다.
6. 바닐라 TypeScript 프로젝트 파일 나누기 예시
예를 들어 Todo 프로젝트를 만든다고 해보겠습니다.
폴더 구조 예시
src/ main.ts types/ todo.ts user.ts api/ auth.ts todo.ts dom/ renderTodo.ts utils/ storage.ts
이 구조는 역할별로 파일을 나눈 것입니다.
types/: 인터페이스, 타입 정의api/: fetch 호출 함수dom/: 화면 렌더링 함수utils/: 공통 유틸 함수main.ts: 전체 실행 시작점
7. 인터페이스를 별도 파일로 분리하기
src/types/todo.ts
export interface Todo { id: number; title: string; done: boolean; } export interface TodoListResponse { success: boolean; data: Todo[]; }
이제 Todo 관련 타입은 이 파일에서 관리할 수 있습니다.
8. API 호출 코드를 분리하기
src/api/todo.ts
import type { TodoListResponse } from "../types/todo"; export async function getTodoList(token: string): Promise<TodoListResponse> { const response = await fetch("https://api.fullstackfamily.com/api/edu/ws-283fc1/todos", { method: "GET", headers: { Authorization: `Bearer ${token}`, }, }); if (!response.ok) { throw new Error("Todo 목록 조회 실패"); } const bodyJson: TodoListResponse = await response.json(); return bodyJson; }
이렇게 하면 API 호출 로직이 화면 코드와 분리됩니다.
9. DOM 렌더링 코드를 분리하기
src/dom/renderTodo.ts
import type { Todo } from "../types/todo"; export function renderTodoList(todoListEl: HTMLUListElement, todos: Todo[]): void { todoListEl.innerHTML = ""; for (const todo of todos) { const li = document.createElement("li"); const titleDiv = document.createElement("div"); titleDiv.textContent = todo.title; li.appendChild(titleDiv); todoListEl.appendChild(li); } }
이 함수는 받은 Todo 배열을 화면에 출력하는 역할만 담당합니다.
10. localStorage 관련 코드도 분리할 수 있다
src/utils/storage.ts
export function getToken(): string | null { return localStorage.getItem("token"); }
작아 보이지만 이런 함수도 분리해두면 나중에 유지보수할 때 훨씬 편합니다.
11. main.ts에서 조립하기
src/main.ts
import { getTodoList } from "./api/todo"; import { renderTodoList } from "./dom/renderTodo"; import { getToken } from "./utils/storage"; async function init(): Promise<void> { const token = getToken(); if (!token) { location.href = "login.html"; return; } const todoListEl = document.getElementById("todoList") as HTMLUListElement | null; if (!todoListEl) { console.error("todoList 요소를 찾을 수 없습니다."); return; } try { const result = await getTodoList(token); renderTodoList(todoListEl, result.data); } catch (error) { console.error("초기화 실패", error); } } init();
main.ts는 전체 흐름만 관리하고, 실제 세부 기능은 각 파일이 맡습니다. 이 방식이 모듈 개발의 핵심입니다.
12. 왜 이렇게 나누는가?
역할이 분리된다
- 타입은
types - API는
api - 화면 출력은
dom - 저장소 접근은
utils
이렇게 역할이 분리되면 파일의 목적이 분명해집니다.
수정이 쉬워진다
예를 들어 API 주소가 바뀌면 api/todo.ts만 수정하면 됩니다.
재사용이 쉬워진다
renderTodoList()는 다른 화면에서도 재사용할 수 있습니다.
협업이 쉬워진다
한 사람은 API 파일을 수정하고, 다른 사람은 DOM 파일을 수정해도 충돌이 줄어듭니다.
13. 바닐라 프로젝트에서 모듈을 사용할 때 주의할 점
브라우저에서 모듈을 사용하려면 보통 script type="module"을 사용합니다.
예를 들어 HTML에서 다음처럼 연결합니다.
<script type="module" src="/src/main.ts"></script>
실제로는 Vite 같은 도구를 사용하면 TypeScript 파일을 더 편하게 관리할 수 있습니다. 브라우저가 TypeScript를 직접 실행하는 것은 아니기 때문에, 개발 환경에서는 보통 Vite가 .ts 파일을 처리해줍니다.
14. 정리
TypeScript의 인터페이스는 데이터 구조를 설명하는 설계도입니다. 특히 API 응답, 사용자 정보, Todo 데이터처럼 형태가 분명한 객체를 다룰 때 큰 도움이 됩니다.
그리고 프로젝트가 커질수록 코드를 한 파일에 몰아넣기보다 모듈 방식으로 나누는 것이 훨씬 유리합니다.
바닐라 프로젝트라고 해서 파일을 나누지 못하는 것은 아닙니다. 오히려 바닐라 프로젝트일수록:
- 타입 정의 분리
- API 분리
- DOM 렌더링 분리
- 유틸 함수 분리
이런 구조를 익혀두면 나중에 React, Vue, Angular 같은 프레임워크로 넘어갈 때도 큰 도움이 됩니다.
결국 중요한 것은 문법 자체보다 코드를 역할별로 나누고, 구조를 명확하게 만드는 습관입니다.
마무리
처음 TypeScript를 배울 때는 interface, export, import가 낯설 수 있습니다. 하지만 하나씩 적용해보면 금방 익숙해집니다.
추천하는 연습 방법은 다음과 같습니다.
- 기존 JavaScript 객체에 인터페이스 붙이기
- API 응답 구조를 인터페이스로 정의하기
main.ts하나에 몰린 코드를 기능별 파일로 나누기export/import로 다시 연결하기
이 흐름을 여러 번 반복해보면 TypeScript와 모듈 방식이 훨씬 자연스럽게 느껴질 것입니다.

댓글
댓글을 작성하려면 이 필요합니다.