프로그램이 완성된 뒤에도 바깥 파일에서 깨진 주문 줄이 섞여 들어오거나, 기프트 카드(Gift) 같은 새 주문 갈래가 더해지기도 합니다. 확인 함수(isOrder)와 판별 유니언·never를 세워 두었다면 깨진 줄은 멈춤 없이 건너뛰고, 새 갈래를 더했을 때는 검사기가 고칠 switch 자리를 빠짐없이 알려 줍니다.
마지막 레슨에서는 깨진 줄이 섞인 입력으로 영수증을 찍어 보고, 새 갈래 Gift를 추가한 뒤 기존 자바스크립트 코드를 타입스크립트로 옮기는 차례와 다음 걸음을 정리합니다.
앞 레슨 코드 펼치기
함수 하나씩 검사하며 만들기에서 완성한 타입 선언입니다.
interface Drink {
kind: "drink";
price: number;
extra: number;
}
interface Food {
kind: "food";
price: number;
}
type Order = Drink | Food;
interface Summary {
total: number;
skipped: number;
}1깨진 주문 줄을 건너뛰고 영수증 찍기
실제 주문 기록에는 끊긴 JSON 글('{"kind":')이나 글자 가격 줄이 섞일 수 있습니다. 앞 레슨의 parseRaw와 isOrder가 이를 어떻게 거르는지 확인합니다.
interface Drink {
kind: "drink";
price: number;
extra: number;
}
interface Food {
kind: "food";
price: number;
}
type Order = Drink | Food;
interface Summary {
total: number;
skipped: number;
}
function isDrink(
v: unknown,
): v is Drink {
return (
typeof v === "object" &&
v !== null &&
"kind" in v &&
v.kind === "drink" &&
"price" in v &&
typeof v.price ===
"number" &&
"extra" in v &&
typeof v.extra === "number"
);
}
function isFood(
v: unknown,
): v is Food {
return (
typeof v === "object" &&
v !== null &&
"kind" in v &&
v.kind === "food" &&
"price" in v &&
typeof v.price === "number"
);
}
function isOrder(
v: unknown,
): v is Order {
return (
isDrink(v) || isFood(v)
);
}
function parseRaw(
s: string,
): unknown {
try {
return JSON.parse(s);
} catch {
return null;
}
}
function cost(o: Order) {
switch (o.kind) {
case "drink":
return o.price + o.extra;
case "food":
return o.price;
default:
const left: never = o;
return left;
}
}
이제 정상 주문 두 줄과 깨진 줄 하나를 넣어 영수증을 찍어 봅니다.
const lines = [
'{"kind":"drink",' +
'"price":4500,"extra":500}',
'{"kind":',
'{"kind":"food",' +
'"price":2000}',
];
let total = 0;
let skip = 0;
for (const s of lines) {
const v = parseRaw(s);
if (isOrder(v)) {
total += cost(v);
} else skip += 1;
}
console.log(`합계 ${total}원`);
console.log(`건너뜀 ${skip}건`);
합계 7000원
건너뜀 1건둘째 줄 '{"kind":'에서 JSON.parse가 던진 오류는 parseRaw가 잡아 null로 바꾸고, isOrder(null)이 false를 돌려주어 프로그램이 멈추지 않고 건너뜀 1건으로 기록됩니다.
2새 갈래 Gift 더하고 never가 짚어 주는 곳 고치기
기프트 카드(kind: "gift", amount: number) 갈래를 새로 더합니다. 타입만 더하고 cost의 switch를 안 고치면 어떻게 될까요?
interface Drink {
kind: "drink";
price: number;
extra: number;
}
interface Food {
kind: "food";
price: number;
}
interface Gift {
kind: "gift";
amount: number;
}
type Order =
| Drink
| Food
| Gift;
function cost(o: Order) {
switch (o.kind) {
case "drink":
return o.price + o.extra;
case "food":
return o.price;
default:
const left: never = o;
return left;
}
}
main.ts(25,13): error TS2322: Type 'Gift' is not assignable to type 'never'.default 갈래의 const left: never = o; 줄(25번 줄)에서 Gift 갈래가 처리되지 않았다고 정확히 짚어 줍니다.
검사기가 알려 준 자리에 case "gift": return o.amount;를 보태면 다시 검사 오류가 0이 됩니다.
interface Drink {
kind: "drink";
price: number;
extra: number;
}
interface Food {
kind: "food";
price: number;
}
interface Gift {
kind: "gift";
amount: number;
}
type Order =
| Drink
| Food
| Gift;
function cost(o: Order) {
switch (o.kind) {
case "drink":
return o.price + o.extra;
case "food":
return o.price;
case "gift":
return o.amount;
default:
const left: never = o;
return left;
}
}
const items: Order[] = [
{
kind: "drink",
price: 4500,
extra: 500,
},
{ kind: "food", price: 2000 },
{
kind: "gift",
amount: 5000,
},
];
let sum = 0;
for (const it of items) {
sum += cost(it);
}
console.log(`합계 ${sum}원`);
합계 12000원3가진 JS 코드를 TS로 옮기는 차례와 다음 걸음
만들어 둔 자바스크립트 프로그램(예: 할 일 앱)을 타입스크립트로 옮길 때는 아래 네 걸음을 차례로 밟습니다.
| 단계 | 할 일 | 확인 기준 |
|---|---|---|
1. 파일 하나씩 .ts로 | 가장 작은 파일 하나만 확장자를 .ts로 바꾸고 tsconfig.json을 둔다 | 바꾼 파일에서 npx tsc 실행 |
| 2. 데이터 모양 먼저 | 프로그램이 다루는 핵심 객체 모양을 interface와 판별 유니언으로 먼저 적는다 | 타입 선언 문법 오류 0 |
| 3. 매개변수와 빈 그릇 | 함수 매개변수(TS7006)와 빈 배열·빈 객체(let list: Todo[] = [])에 타입을 붙인다 | 함수마다 npx tsc 오류 0 |
4. 바깥 경계와 strict | JSON.parse나 querySelector 자리에 확인 함수·instanceof를 넣고 strict 검사를 통과시킨다 | 전체 프로젝트 검사 오류 0 + 실행 확인 |
열한 단원을 통해 일곱 가지 검사 규칙과 설계법을 모두 익혔습니다. 다음 걸음으로는 props와 상태(useState)를 타입으로 설계하는 React, 또는 서버와 브라우저의 데이터 경계를 잇는 Next.js와 Node 백엔드로 나아갈 수 있습니다.
예제
검사 결과와 실행 결과를 먼저 적어 본 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.
할 일 앱의 자바스크립트 코드를 .ts로 옮기면서, 할 일 모양 Todo를 선언하고 완료되지 않은 할 일만 세는 countLeft 함수를 적었습니다.
interface Todo {
title: string;
done: boolean;
}
function countLeft(
list: Todo[],
): number {
let left = 0;
for (const t of list) {
if (!t.done) left += 1;
}
return left;
}
const todos: Todo[] = [
{ title: "우유", done: true },
{ title: "운동", done: false },
];
console.log(countLeft(todos));
검사를 지나갈까요? 실행하면 무엇이 찍힐까요?
검사 결과와 실행 결과 보기
1interface Todo로 모양을 먼저 적고 매개변수에 list: Todo[]를 붙였으므로 검사 오류 0으로 통과하고, 완료되지 않은 할 일 개수인 1이 출력됩니다.
자주 틀리는 곳
틀린 생각: 기존 자바스크립트 폴더의 모든 .js 파일을 한꺼번에 .ts로 바꾸고 시작해야 한다.
파일 열 개를 한꺼번에 .ts로 바꾸면 검사 오류가 쏟아져 어디부터 고칠지 찾기 어렵습니다.
맞는 규칙: 다른 파일에 의존하지 않는 작은 유틸리티나 데이터 파일 하나부터 .ts로 바꾸어 검사 오류 0을 만든 뒤 하나씩 넓혀 갑니다.
틀린 생각: 처음부터 noUncheckedIndexedAccess 같은 추가 엄격 옵션까지 한꺼번에 다 켜고 옮긴다.
첫날부터 추가 옵션을 다 켜면 핵심 타입보다 부수적인 인덱스 오류를 고치는 데 시간을 쓰게 됩니다.
맞는 규칙: 먼저 과목 기본 설정("strict": true)을 통과시킨 뒤, 프로그램이 안정되면 noUncheckedIndexedAccess 같은 추가 옵션을 하나씩 켜서 빈틈을 조입니다.
틀린 생각: 옮기는 도중 귀찮은 자리는 일단 any나 as로 두고 나중에 고치면 된다.
'임시'로 적은 any와 as는 빨간 줄이 사라져 나중에 어디에 적었는지 잊어버리기 쉽습니다(검사 규칙 6).
맞는 규칙: 아직 정확한 모양을 모르는 바깥 값은 any 대신 **unknown**으로 적어 두어야, 나중에 그 값을 쓰는 자리에서 검사기가 확인 함수를 짜도록 일깨워 줍니다.
확인 문제
1. 1절에서 둘째 줄 '{"kind":'처럼 문법이 깨진 JSON 글이 들어왔을 때 프로그램이 멈추지 않고 건너뜀 1건으로 넘어갈 수 있었던 까닭은 무엇인가요?
힌트
parseRaw 안의 try ... catch와 isOrder(null)의 결과를 차례로 따라가 보세요.
정답
parseRaw가 try ... catch로 SyntaxError를 잡아 null을 돌려주고, 이어서 isOrder(null)이 false를 돌려주어 else 갈래(skip += 1)로 안전하게 넘어갔기 때문입니다.
2. 2절에서 type Order = Drink | Food에 | Gift를 더했을 때, cost 함수의 어느 줄이 왜 오류를 내어 고칠 곳을 알려 주었나요?
힌트
switch (o.kind)의 마지막 default 갈래에 적힌 타입을 떠올려 보세요.
정답
default 갈래의 const left: never = o; 줄에서 Gift를 never에 넣을 수 없다는 TS2322 오류가 났습니다. case "gift"를 아직 적지 않아 default까지 내려온 o의 지금 타입이 Gift가 되었기 때문입니다.
3. 기존 자바스크립트 코드를 타입스크립트로 옮길 때 아직 정확한 모양을 다 파악하지 못한 외부 입력 자리가 있다면, any와 unknown 가운데 무엇을 적어 두어야 왜 안전할까요?
힌트
나중에 그 변수의 속성을 읽으려 할 때 검사기가 알려 주는지(TS18046) 생각해 보세요.
정답
unknown을 적어 두어야 안전합니다. any를 적어 두면 검사가 완전히 꺼져 잊어버리기 쉽지만, unknown을 적어 두면 그 값을 확인 없이 쓰려 할 때마다 검사기가 TS18046으로 알려 주어 확인 함수를 빠뜨리지 않게 해 줍니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
2절의 완성 코드에 무료 쿠폰 갈래(kind: "coupon")를 하나 더 추가해 보고, npx tsc가 가리키는 줄을 따라 case "coupon": return 0;을 보태 검사 오류 0으로 통과시켜 보세요.
검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.