프로그램이 커지면 타입 선언과 함수를 여러 파일로 나누어 담습니다. 타입스크립트에서는 값(const, function)뿐 아니라 interface나 type 별칭 앞에도 export를 붙여 내보낼 수 있고, 다른 파일에서 타입을 가져올 때는 import type을 써서 실행 전에 통째로 지워질 줄임을 분명히 표시합니다.
메뉴 모양(Menu)과 가격(latte)을 menu.ts에서 내보내고 main.ts에서 가져오는 흐름을 따라가며, 왜 타입에는 import type을 쓰고 경로 끝에 .ts 확장자를 붙이는지 살펴봅니다.
JS 다시 보기: 파일 나누기: import와 export에서 파일마다 값을 내보내고 가져오는 기본 문법을 다뤘습니다.
이 단원을 이미 안다면 단원 연습 문제의 진단 문항부터 풀고 틀린 레슨만 보세요.
1타입과 값을 내보내고 가져오는 법
값과 마찬가지로 interface나 type 선언 앞에도 export를 붙이면 다른 파일에서 가져다 쓸 수 있습니다. 먼저 menu.ts 파일에 Menu 인터페이스와 latte 가격 상수를 내보냅니다.
// menu.ts
export interface Menu {
item: string;
price: number;
}
export const latte = 4500;
이제 main.ts에서 Menu 타입과 latte 값을 가져옵니다. 이때 타입은 import type으로, 실행 때 쓰이는 값은 일반 import로 구분해 적습니다.
// main.ts
import type {
Menu,
} from "./menu.ts";
import {
latte,
} from "./menu.ts";
const m: Menu = {
item: "라테",
price: latte,
};
console.log(m.item, m.price);
한 줄에 함께 가져오고 싶다면 타입 이름 앞에만 type 키워드를 붙이는 인라인 표기({ type Menu, latte })를 써도 됩니다.
// main.ts (한 줄로 합친 꼴)
import {
type Menu,
latte,
} from "./menu.ts";
2왜 import type을 따로 구분할까?
Node가 .ts 파일을 바로 실행할 때는 각 파일의 타입 표기를 지우기만 합니다.
menu.ts의export interface Menu { ... }는 타입 지우기 단계에서 흔적 없이 사라집니다. 실행 시점의menu.ts에는export const latte = 4500;한 줄만 남습니다.main.ts의import type { Menu }줄도 통째로 지워지므로, Node는latte값만 안전하게 가져와라테 4500을 찍습니다.
만약 type을 빼먹고 import { Menu }처럼 일반 값 import로 적으면 어떻게 될까요?
- 타입 검사(
npx tsc): 과목 설정의verbatimModuleSyntax가 타입은 반드시 타입 전용 가져오기로 적으라며TS1484오류로 즉시 막아 줍니다. - Node 실행(
node main.ts):import { Menu }줄이 지워지지 않고 남아서, 이미Menu가 지워진menu.ts에서 실행 값을 찾다가SyntaxError로 멈춥니다.
| 가져오는 대상 | 쓰는 문법 | 타입 지우기 뒤 |
|---|---|---|
interface · type 별칭 | import type \{ Menu \} | 줄 통째로 사라짐 |
const · function · class(값) | import \{ latte \} | 그대로 남아 실행됨 |
| 한 줄에 섞어 가져올 때 | import \{ type Menu, latte \} | type Menu만 사라지고 latte만 남음 |
예제
실행 결과를 먼저 적어 본 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.
Order 인터페이스와 calc 함수를 정의하고 주문 금액을 계산합니다.
interface Order {
item: string;
qty: number;
}
function calc(o: Order) {
return o.qty * 4000;
}
const myOrder: Order = {
item: "커피",
qty: 2,
};
console.log(calc(myOrder));
무엇이 찍힐까요? 또한 Order와 calc를 다른 파일에서 한 줄로 함께 가져온다면 어떻게 적을까요?
실행 결과 보기
8000Order는 타입 지우기 때 사라지는 인터페이스이므로 다른 파일에서 가져올 때 앞에 type을 붙여 { type Order, calc }처럼 적고, 실행 때 호출할 함수 calc는 일반 이름으로 가져옵니다.
자주 틀리는 곳
틀린 생각: 타입을 import하면 실행 때에도 어떤 객체가 넘어온다.
import type { Menu }로 가져온 Menu는 오직 : Menu 같은 타입 자리에만 쓸 수 있습니다(타입이 실행 때 남는다는 생각). 값 자리에 console.log(Menu)처럼 쓰면 타입을 값으로 썼다며 TS2693 오류가 납니다.
interface Menu {
item: string;
price: number;
}
console.log(Menu);
main.ts(6,13): error TS2693: 'Menu' only refers to a type, but is being used as a value here.맞는 규칙: interface와 type 별칭은 실행 전에 지워지므로 값 자리에 놓을 수 없고, 가져올 때도 import type으로 구분해 적습니다.
틀린 생각: Node로 .ts 파일을 직접 돌릴 때도 파일 경로 끝에 확장자를 생략하거나 .js로 적는다.
확장자를 빼먹고 "./menu"만 적으면 검사기는 TS2835 오류를 내고, Node도 파일을 찾지 못해 멈춥니다.
맞는 규칙: Node가 .ts 파일을 직접 실행하는 환경에서는 실제 파일 이름 그대로 **./menu.ts**까지 적어야 합니다(과목 tsconfig.json의 allowImportingTsExtensions 옵션이 이를 허락합니다). 또한 작업 폴더의 package.json에는 "type": "module"이 들어 있어야 합니다(없으면 TS1295).
확인 문제
1. menu.ts에 선언된 export interface Menu를 main.ts에서 type 없이 import { Menu }로 가져오면 Node 실행 때 왜 멈출까요?
힌트
타입 지우기 단계 뒤에 menu.ts와 main.ts에 각각 무엇이 남는지 생각해 보세요.
정답
타입 지우기 단계에서 menu.ts 안의 interface Menu가 사라지기 때문입니다. 반면 main.ts의 일반 import { Menu } 줄은 지워지지 않고 남아, 실행 시점에 존재하지 않는 Menu 값을 찾다가 SyntaxError로 멈춥니다.
2. 한 줄에서 타입 Menu와 값 상수 latte를 함께 가져오려면 어떻게 적나요?
힌트
1절 마지막 코드 블록의 인라인 type 표기를 살펴보세요.
정답
{ type Menu, latte }처럼 타입 이름 앞에만 type을 붙여 적습니다. 그러면 지우기 단계에서 type Menu만 지워지고 값 latte만 남습니다.
3. Node로 .ts 파일을 직접 실행하는 이 과목 환경에서 다른 파일을 가져올 때 경로 끝에 어떤 확장자를 적어야 하나요?
힌트
작업 폴더에 실제로 존재하는 파일의 확장자가 무엇인지 떠올려 보세요.
정답
실제 파일 확장자인 .ts("./menu.ts")를 적어야 합니다. 과목 tsconfig.json의 allowImportingTsExtensions 설정 덕분에 .ts 경로가 검사와 실행 양쪽에서 모두 그대로 동작합니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
작업 폴더에 1절의 menu.ts와 main.ts 두 파일을 저장하고 npx tsc와 node main.ts를 차례로 실행해 보세요. 다음 레슨 선언 파일과 @types에서는 외부 환경의 타입을 알려 주는 .d.ts 파일을 배웁니다.
검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.