이번 편의 결과물: models/expense.ts에 거래 필드를 원시 타입으로 정의하고, 하드코딩된 거래 1건이 화면에 출력됩니다. · 다루는 개념: 원시 타입, 타입 애너테이션과 타입 추론, 배열과 튜플, 리터럴 타입
이 편에서 만드는 파일
expense-tracker/src/
├── models/
│ └── expense.ts (+)
└── main.ts (~)개념 정리
원시 타입
TypeScript의 기본 타입은 JavaScript의 원시 값 종류를 그대로 따릅니다.
| 타입 | 예시 값 |
|---|---|
string | '식비' |
number | 12000 |
boolean | true |
null | null |
undefined | undefined |
타입 애너테이션과 타입 추론
변수 선언 뒤에 : 타입을 붙이는 것이 타입 애너테이션입니다. 초기값이 있으면 TypeScript가 그 값을 보고 타입을 스스로 알아내는데, 이것이 타입 추론입니다.
let count: number = 0 // 애너테이션 — 타입을 직접 명시
let price = 12000 // 추론 — TypeScript가 number로 판단초기값만으로 타입이 분명하면 추론에 맡겨도 되지만, 함수 매개변수처럼 초기값이 없는 자리는 반드시 애너테이션이 필요합니다(04편에서 다룸). 이 편에서는 모델 파일의 의도를 분명히 남기기 위해 애너테이션을 명시합니다.
배열과 튜플
string[]처럼 쓰면 같은 타입의 값을 몇 개든 담을 수 있는 배열입니다. 반면 튜플은 길이와 각 위치의 타입이 고정된 배열입니다.
| 표기 | 의미 |
|---|---|
string[] | 문자열이 몇 개든 들어갈 수 있는 배열 |
[string, number] | 정확히 2개, 첫 번째는 문자열·두 번째는 숫자로 고정된 튜플 |
[id: number, date: string] | 튜플에 이름표(label)를 붙여 각 자리의 의미를 표시 |
이름표는 타입 검사에 영향을 주지 않고, 편집기에서 각 자리의 의미를 보여주는 역할만 합니다.
리터럴 타입
string 타입은 아무 문자열이나 허용하지만, 리터럴 타입은 정확히 그 값 하나만 허용합니다.
let currencyA: string = 'KRW' // 어떤 문자열이든 나중에 대입 가능
let currencyB: 'KRW' = 'KRW' // 'KRW' 외에는 대입 불가능실습
1. models/expense.ts 작성
// expense-tracker/src/models/expense.ts
// 원시 타입 애너테이션 — 초기값이 있어 추론에 맡겨도 되지만,
// 모델 파일에서는 각 필드의 타입을 명시적으로 남깁니다.
export const sampleId: number = 1
export const sampleDate: string = '2026-09-20'
export const sampleCategory: string = '식비'
export const sampleAmount: number = 12000
export const sampleMemo: string = '점심 식사'
// 리터럴 타입 — 'KRW' 한 값만 허용합니다.
export const CURRENCY: 'KRW' = 'KRW'
// 배열 타입 — 카테고리는 몇 개든 늘어날 수 있습니다.
export const CATEGORIES: string[] = ['식비', '교통', '문화', '주거', '기타']
// 튜플 타입 — 순서와 각 자리 타입이 고정됩니다. 이름표로 의미를 표시합니다.
export type ExpenseRow = [id: number, date: string, category: string, amount: number, memo: string]
export const sampleExpenseRow: ExpenseRow = [sampleId, sampleDate, sampleCategory, sampleAmount, sampleMemo]2. main.ts에서 화면에 출력
// expense-tracker/src/main.ts
import './style.css'
import { CATEGORIES, CURRENCY, sampleExpenseRow } from './models/expense.ts'
function formatExpenseRow(row: typeof sampleExpenseRow): string {
const [id, date, category, amount, memo] = row
return `#${id} · ${date} · ${category} · ${amount.toLocaleString()}${CURRENCY} · ${memo}`
}
const app = document.querySelector<HTMLDivElement>('#app')
if (app) {
app.innerHTML = `
<main>
<h1>가계부</h1>
<p>사용 가능한 카테고리: ${CATEGORIES.join(', ')}</p>
<ul>
<li>${formatExpenseRow(sampleExpenseRow)}</li>
</ul>
</main>
`
}formatExpenseRow의 매개변수 타입을 typeof sampleExpenseRow로 쓰면, models/expense.ts가 만든 sampleExpenseRow와 정확히 같은 튜플 타입을 별도로 이름 붙이지 않고 재사용할 수 있습니다. 튜플을 const [id, date, category, amount, memo] = row로 구조 분해하면 각 변수가 원래 자리의 타입(number, string, string, number, string)을 그대로 받습니다.
3. 실행
npm run dev확인
- 화면에 “사용 가능한 카테고리: 식비, 교통, 문화, 주거, 기타”가 보입니다.
- 목록에 “#1 · 2026-09-20 · 식비 · 12,000KRW · 점심 식사” 한 줄이 보입니다.
npx tsc --noEmit을 실행해도 오류가 없습니다.
직접 해보기
CATEGORIES배열에 새 카테고리(예:'의료')를 추가하고, 화면의 카테고리 목록에 바로 반영되는지 확인합니다.sampleExpenseRow의 네 번째 자리(금액)에 숫자 대신 문자열'12000'을 넣어 보고, 편집기가 어떤 오류를 표시하는지 확인한 뒤 원래 값으로 되돌립니다.
정답 보기
ExpenseRow의 네 번째 자리는 amount: number로 고정되어 있으므로, 문자열 '12000'을 넣으면 “Type ‘string’ is not assignable to type ‘number’” 오류가 그 자리에 표시됩니다. 튜플은 값의 개수뿐 아니라 각 자리의 타입까지 고정한다는 것을 보여주는 예시입니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| 튜플 값의 순서를 바꿨더니 오류가 남 | ExpenseRow는 자리마다 타입이 고정된 튜플이라 순서를 바꾸면 타입이 안 맞음 | 정의한 순서(id, date, category, amount, memo)를 그대로 지킨다 |
CURRENCY에 다른 문자열을 대입하려다 오류가 남 | 리터럴 타입 'KRW'는 그 값 하나만 허용함 | 통화를 늘리려면 'KRW' | 'USD'처럼 유니온으로 바꿔야 한다(06편에서 다룸) |
string[]과 Array<string>을 다른 타입으로 오해함 | 둘은 같은 배열 타입을 쓰는 방식만 다른 표기법 | 어느 쪽을 써도 동작은 같으며, 이 과목은 string[] 표기를 사용한다 |