본문으로 건너뛰기

단원 연습 문제

타입스크립트 목차갈래가 있는 데이터문제 12개

5단원 '갈래가 있는 데이터'의 세 레슨에서 아홉 문제를 내고, 앞 단원(3 · 4단원)에서 세 문제를 섞었습니다. 앞 단원 문제가 어느 레슨인지는 정답을 펼치면 보입니다. 검사 결과는 지나가는지와 막히는 줄을, 지금 타입은 검사기가 찍는 글자를, 실행 결과는 찍히는 글자를 종이에 끝까지 쓴 뒤 정답을 펼치세요. 실행이 오류로 멈추면 오류 이름이 든 줄까지만 써도 됩니다. 막히면 정답 앞의 힌트를 먼저 펼칩니다. 점수는 매기지 않습니다.

한 번에 다 풀 필요는 없습니다. 문제 1~10은 폰으로도 풀 수 있고(약 30~40분), 끝의 만들어 보기 둘은 컴퓨터에서 tsc와 Node로 직접 검사하고 돌려 봅니다. 이 단원을 건너뛸지 보려면 진단 문항인 문제 1 · 2 · 5 · 8 넷을 먼저 풀어 보세요(약 15분). 진단 문항은 힌트를 펴지 않고 풉니다. 힌트를 펴고 맞힌 문제나 짐작으로 맞힌 문제는 틀린 것으로 칩니다. 모두 맞으면 이 단원 레슨은 건너뛰고, 틀린 문제가 있으면 그 정답 끝의 레슨만 봅니다. 끝의 만들어 보기 둘은 골라서 풉니다.

타입 선언이 긴 문제는 코드를 두 블록으로 나눴습니다. 둘째 블록은 첫째 블록에 이어 쓴 것이고 줄 번호도 이어서 셉니다. 끝의 두 문제는 갈래마다 처리하는 함수를 직접 만들어 봅니다. 컴퓨터가 있으면 블록을 차례로 main.ts에 이어 붙여 npx tsc로 검사 오류가 0인지 본 뒤 node main.ts로 돌려 맞춰 봅니다. 플레이그라운드에서는 맨 위에 export {}; 한 줄을 더합니다. 폰으로만 본다면 문제에 적힌 값으로 내 코드를 한 줄씩 검사하고 따라가 나와야 할 화면과 맞춰 봅니다. 검사 결과는 tsc 7.0.2로, 실행 결과는 Node 26으로 돌린 것입니다.

문제 1

달리기와 수영 기록을 한 타입으로 받습니다.

interface Run {
  kind: 'run';
  km: number;
}
interface Swim {
  kind: 'swim';
  laps: number;
}
type Workout = Run | Swim;

위 타입 선언에 이어 씁니다. 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.

function entry(w: Workout) {
  const n = w.laps;
  if (w.kind === 'run') {
    return w.km + 'km';
  }
  return w.laps + '바퀴';
}
힌트

11 · 13 · 15번 줄마다 그 줄에서 w가 어느 갈래일 수 있는지 봅니다.

정답
검사 결과 오류 1개
main.ts(11,15): error TS2339: Property 'laps' does not exist on type 'Workout'.  Property 'laps' does not exist on type 'Run'.

11번 줄에서만 막힙니다.

  • 11번 줄: 아직 갈래 속성을 확인하기 전이라 w는 Workout(Run이거나 Swim)입니다. laps는 Swim에만 있어 막힙니다. 들여 쓴 줄이 'Run에는 없다'고 짚습니다.
  • 13번 줄: w.kind === 'run' 확인 뒤의 갈래라 w가 Run으로 좁혀져 km를 씁니다.
  • 15번 줄: run 갈래는 return으로 끝나 이 줄에 오지 않습니다. 오는 길은 거짓 길 하나라 w는 Swim이고 laps를 씁니다(검사 규칙 5).

흔한 오답 하나는 '지나감'입니다. Workout에 laps를 가진 갈래가 있으니 쓸 수 있다고 본 것입니다. 유니언에서는 모든 갈래가 함께 가진 속성만 확인 없이 씁니다. 15번 줄까지 막힌다고 쓰면 run 갈래를 지나 15번 줄에 오면 w가 다시 Workout이라고 본 것입니다(갈래를 나오면 선언 타입이라는 생각).

다시 볼 레슨: 판별 유니언 · 좁히기 — typeof와 타입 추적 표

문제 2

포장과 배달 주문을 한 타입으로 받습니다.

interface Pickup {
  kind: 'pickup';
  wait: number;
}
interface Delivery {
  kind: 'delivery';
  fee: number;
  memo?: string;
}
type Ship = Pickup | Delivery;

위 타입 선언에 이어 씁니다. 14번 줄과 16번 줄에서 s의 지금 타입은 각각 무엇일까요?

function note(s: Ship) {
  switch (s.kind) {
    case 'pickup':
      return s.wait + '분';
    case 'delivery':
      return '배달비 ' + s.fee;
  }
}
힌트

14번 줄과 16번 줄이 각각 어느 case 아래에 있는지 봅니다.

힌트 둘

switch의 case마다 s.kind가 어느 값인지 정해집니다. 그 값을 가진 갈래가 무엇인지 봅니다.

정답
검사 결과 오류 없음

검사 오류 없이 지나가고, 14번 줄의 s는 Pickup, 16번 줄의 s는 Delivery입니다. switch의 case는 갈래 속성 확인과 같아서, case 'pickup': 아래에서는 kind가 'pickup'인 갈래 Pickup으로 좁혀집니다(검사 규칙 5). 그래서 14번 줄은 wait를, 16번 줄은 fee를 씁니다.

흔한 오답은 둘 다 Ship으로 쓴 것입니다. case로 나눠도 검사기가 s를 좁히지 않는다고 본 것입니다. 그러면 14번 줄의 wait가 막혀야 하는데, 검사 결과에 오류가 없습니다.

다시 볼 레슨: 판별 유니언

문제 3

포장 주문을 Ship 타입 자리에 넣습니다. 가게의 번호표 타입 Ticket도 함께 적었습니다.

interface Pickup {
  kind: 'pickup';
  wait: number;
}
interface Delivery {
  kind: 'delivery';
  fee: number;
  memo?: string;
}
type Ship = Pickup | Delivery;
interface Ticket {
  kind: 'pickup';
  wait: number;
  no: number;
}

위 타입 선언에 이어 씁니다. 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.

const t: Ticket = {
  kind: 'pickup',
  wait: 5,
  no: 12,
};
const a: Ship = t;
const b: Ship = {
  kind: 'pickup',
  wait: 5,
  fee: 0,
};
힌트

21번 줄과 22~26번 줄에서 Ship 자리에 넣는 값을 하나씩 봅니다.

힌트 둘

21번 줄의 t는 이름을 거친 값이고, 22~26번 줄은 Ship 자리에 바로 새로 쓴 객체입니다. 새로 쓴 객체는 23번 줄의 kind 값으로 갈래가 정해지고, 그 갈래가 가진 속성 줄과 견줍니다.

정답
검사 결과 오류 1개
main.ts(25,3): error TS2353: Object literal may only specify known properties, and 'fee' does not exist in type 'Pickup'.

25번 줄에서만 막힙니다.

  • 21번 줄은 지나갑니다. t의 타입 이름은 Ticket이지만, Pickup 갈래가 요구하는 kind · wait 줄을 다 가졌습니다. no 줄이 더 있어도 이름 t를 거친 값이라 오류가 아닙니다. 검사기는 타입 이름이 아니라 모양으로 맞춥니다(검사 규칙 4).
  • 22~26번 줄의 객체는 Ship 자리에 바로 새로 쓴 것입니다. kind가 'pickup'이라 Pickup 갈래로 맞춰지고, Pickup에는 fee 줄이 없어 남는 속성 fee(25번 줄)를 오타일 수 있다고 잡습니다(검사 규칙 4).

흔한 오답 하나는 21번 줄도 막힌다고 쓴 것입니다. Ticket이 Ship의 갈래 이름이 아니라서 넣을 수 없다고 본 것입니다(이름이 같아야 맞는다는 생각). no 줄이 남아서 막힌다고 봤다면 남는 속성을 어디서나 오류로 본 것입니다(남는 속성은 늘 오류라는 생각). 다른 흔한 오답은 '지나감'입니다. Ship의 다른 갈래(Delivery)에 fee가 있으니 넣어도 된다고 본 것입니다. 남는 속성은 그 객체가 맞춰진 갈래를 기준으로 봅니다.

다시 볼 레슨: 모양으로 맞추기 · 새로 쓴 객체의 남는 속성 · 판별 유니언

문제 4

수영이면 바퀴 수를 먼저 찍고 거리를 돌려주려 합니다.

interface Run {
  kind: 'run';
  km: number;
}
interface Swim {
  kind: 'swim';
  laps: number;
}
type Workout = Run | Swim;

위 타입 선언에 이어 씁니다. 14번 줄에서 w의 지금 타입은 무엇일까요? 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.

function entry(w: Workout) {
  if (w.kind === 'swim') {
    console.log(w.laps + '바퀴');
  }
  return w.km + 'km';
}
힌트

타입 추적 표에 10 · 11 · 12 · 14번 줄의 w를 적어 봅니다.

힌트 둘

11번 줄의 갈래가 return으로 끝나는지 봅니다. 14번 줄로 오는 길이 몇 개인지 셉니다.

정답

14번 줄에서 w의 지금 타입은 Workout이고, 검사는 14번 줄에서 막힙니다.

검사 결과 오류 1개
main.ts(14,12): error TS2339: Property 'km' does not exist on type 'Workout'.  Property 'km' does not exist on type 'Swim'.
줄이름지금 타입
10wWorkout
11wWorkout → 참 Swim 거짓 Run
12wSwim
14w만나는 줄 — 참 Swim + 거짓 Run → Workout

11번 줄의 갈래는 return 없이 끝나, Swim인 길도 14번 줄로 옵니다. 두 길이 합쳐져 w는 다시 Run이거나 Swim이고, Swim에는 km가 없어 막힙니다(검사 규칙 5).

같은 답으로 치는 꼴: Run | Swim(Workout이 그 이름입니다).

흔한 오답은 Run으로 쓰고 '지나감'이라고 쓴 것입니다. 수영 갈래를 지나면 남는 것은 달리기라고 본 것입니다(좁힘이 끝까지 간다는 생각). 그렇게 되는 것은 갈래가 return으로 끝날 때뿐입니다.

다시 볼 레슨: 좁히기 — typeof와 타입 추적 표 · 판별 유니언

문제 5

결제 방법마다 수수료를 돌려줍니다.

type Pay =
  | 'card'
  | 'cash'
  | 'point';

위 타입 선언에 이어 씁니다. 12번 줄에서 p의 지금 타입은 무엇일까요? 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.

function fee(p: Pay) {
  switch (p) {
    case 'card':
      return 100;
    case 'cash':
      return 0;
    default:
      const left: never = p;
      return left;
  }
}
console.log(fee('card'));
힌트

Pay의 값 셋 가운데 case로 처리하지 않은 값이 있는지 봅니다.

정답

12번 줄에서 p의 지금 타입은 "point"이고, 검사는 12번 줄에서 막힙니다.

검사 결과 오류 1개
main.ts(12,13): error TS2322: Type '"point"' is not assignable to type 'never'.

case 'card'와 case 'cash'를 지나면 default로 오는 값은 'point' 하나뿐입니다. never는 값이 하나도 없는 모음이라 "point"를 넣을 수 없습니다. 이 오류가 '처리하지 않은 갈래 point가 남았다'는 알림입니다.

같은 답으로 치는 꼴: 'point'처럼 작은따옴표로 쓴 것.

흔한 오답은 '지나감'입니다. 16번 줄은 'card'만 넘기니 default는 실행되지 않고, 그래서 검사기도 읽지 않는다고 본 것입니다(검사기가 실행을 따라간다는 생각). 검사기는 값을 모르니 default로 오는 길까지 읽습니다. p를 never로 쓰면 point가 빠진 것을 놓친 것입니다.

다시 볼 레슨: 빠짐없이 — never로 갈래 검사

문제 6

도서관 자리마다 쓸 수 있는 시간(분)을 돌려줍니다. 스터디 룸(room)은 120분이어야 합니다.

type Seat =
  | 'bar'
  | 'table'
  | 'room';

위 타입 선언에 이어 쓴 코드가 검사에서 막힙니다.

function minutes(s: Seat) {
  switch (s) {
    case 'bar':
      return 60;
    case 'table':
      return 90;
    default:
      const left: never = s;
      return left;
  }
}
console.log(minutes('room'));
검사 결과 오류 1개
main.ts(12,13): error TS2322: Type '"room"' is not assignable to type 'never'.

아래 고치기 가운데 검사 오류를 없애는 것을 모두 고르세요. 그 가운데 minutes('room')이 120을 찍는 것도 모두 고르세요.

  • ① 11번 줄 앞에 case 'room':과 return 120; 두 줄을 더한다
  • ② 11~13번 줄(default와 그 아래 두 줄)을 지운다
  • ③ 12번 줄을 const left: string = s;⁠로 고친다
힌트

고친 코드마다 room이 어느 줄로 가서 무엇을 돌려주는지 따라가 봅니다.

정답

검사 오류를 없애는 것은 ①②③ 셋 모두이고, 120을 찍는 것은 ①뿐입니다.

type Seat =
  | 'bar'
  | 'table'
  | 'room';

①로 고친 코드를 위 타입 선언에 이어 씁니다.

function minutes(s: Seat) {
  switch (s) {
    case 'bar':
      return 60;
    case 'table':
      return 90;
    case 'room':
      return 120;
    default:
      const left: never = s;
      return left;
  }
}
console.log(minutes('room'));
실행 결과
120

never 줄의 오류는 빠진 갈래를 알려 주는 것이라, 빠진 case를 채우는 것이 고치기입니다. 고친 뒤에도 never 줄이 남아, 다음에 자리 종류가 늘면 또 알려 줍니다.

  • ②는 오류를 내던 줄을 지운 것입니다. 검사는 지나지만 room은 어느 case에도 걸리지 않아 아무것도 돌려주지 않고 undefined를 찍습니다. 다음에 갈래가 늘어도 알려 줄 줄이 없습니다.
  • ③은 left의 타입을 넓혀 오류만 지운 것입니다. room이 default로 가 글자 'room'을 그대로 돌려줘 room을 찍습니다.

흔한 오답은 ②나 ③까지 고른 것입니다. never 줄의 오류를 없애야 할 걸림돌로 본 것입니다. 오류만 지우면 검사는 지나도 결과는 틀립니다(검사를 지나면 맞다는 생각).

다시 볼 레슨: 빠짐없이 — never로 갈래 검사

문제 7

배달 주문이면 메모를 다듬어 돌려줍니다.

interface Pickup {
  kind: 'pickup';
  wait: number;
}
interface Delivery {
  kind: 'delivery';
  fee: number;
  memo?: string;
}
type Ship = Pickup | Delivery;

위 타입 선언에 이어 씁니다. 검사 결과와 실행 결과를 각각 쓰세요.

function note(s: Ship) {
  if (s.kind === 'delivery') {
    return s.memo.trim();
  }
  return '포장';
}
const d: Ship = {
  kind: 'delivery',
  fee: 3000,
};
console.log(note(d));
힌트

13번 줄에서 s가 어느 갈래로 좁혀지는지, 그 갈래의 memo 줄에 무엇이 붙어 있는지 봅니다.

정답
검사 결과 오류 1개
main.ts(13,12): error TS18048: 's.memo' is possibly 'undefined'.
실행 결과
main.ts:13    return s.memo.trim();                  ^ TypeError: Cannot read properties of undefined (reading 'trim')    at note (main.ts:13:19)    at main.ts:21:13

검사는 13번 줄에서 막힙니다. 갈래 확인으로 s는 Delivery로 좁혀지지만, Delivery의 memo는 선택 속성이라 지금 타입이 string | undefined입니다. 이 TS18048은 memo가 undefined일 수 있다는 뜻입니다. Node는 그대로 돌다 d에 memo가 없어 13번 줄에서 멈춥니다.

흔한 오답은 '지나감'과 실행 결과 '포장' 또는 빈 문자열입니다. 갈래를 좁히면 그 갈래의 속성은 다 있다고 본 것입니다(꺼낸 값은 늘 있다는 생각). 좁혀도 선택 속성은 따로 거릅니다 — 예를 들어 s.memo ?? ''.

다시 볼 레슨: 선택 속성과 readonly · null·undefined 거르기 · 판별 유니언

문제 8

차와 케이크를 갈래로 나눠 값을 셈합니다. 케이크는 5000원에 상자 값 500원을 더해 5500원이어야 합니다.

interface Tea {
  kind: 'tea';
  won: number;
}
interface Cake {
  kind: 'cake';
  won: number;
  box: number;
}
type Item = Tea | Cake;

위 타입 선언에 이어 씁니다. 검사 결과와 실행 결과를 각각 쓰세요.

function price(i: Item) {
  switch (i.kind) {
    case 'tea':
      return i.won;
    case 'cake':
      return i.won - i.box;
  }
}
const cake: Item = {
  kind: 'cake',
  won: 5000,
  box: 500,
};
console.log(price(cake));
힌트

cake를 넘긴 price가 어느 case의 return에 닿는지 따라가 봅니다.

힌트 둘

손으로 5000과 500을 16번 줄대로 셈해 봅니다.

정답
검사 결과 오류 없음
실행 결과
4500

검사는 오류 없이 지나지만 뜻한 5500이 아니라 4500이 찍힙니다. 이렇게 다르게 찍히면 처음 어긋나는 줄을 찾습니다 — 여기서는 16번 줄입니다. 상자 값을 더해야 하는데 뺐습니다. return i.won + i.box;⁠로 고치면 5500입니다. i.won과 i.box가 둘 다 number라 빼기도 타입이 맞아, 검사기는 알려 주지 않습니다(검사 규칙 1).

흔한 오답은 실행 결과를 5500으로 쓴 것입니다. 검사를 지났으니 합계도 맞다고 본 것입니다(검사를 지나면 맞다는 생각). 검사 오류가 0이 된 뒤에도 작은 값으로 손으로 셈한 값과 맞춰 봅니다.

다시 볼 레슨: 작은 주문표 만들기 · 타입 검사기가 하는 일

문제 9

하고 싶은 일마다 쓸 타입이나 낱말을 쓰세요.

  • (가) 값이 하나도 들지 않는 타입
  • (나) Order 객체 여럿을 담는 배열의 타입
  • (다) switch에서 앞의 case 어디에도 걸리지 않은 값이 가는 자리를 여는 낱말
  • (라) 갈래 속성 값을 확인할 때 쓰는, 값의 종류까지 같은지 견주는 연산자
힌트

(나)는 2단원에서 number[]을 적은 꼴과 같습니다.

정답

받는 답: (가) never · (나) Order[] · (다) default · (라) ===

틀리는 꼴:

  • (가) void — 돌려주는 값이 없는 함수의 타입입니다. never는 값이 하나도 없는 모음이라 어떤 값도 넣을 수 없습니다.
  • (나) [Order] — Order 한 칸짜리 튜플입니다. Order만 쓰면 주문 하나입니다.
  • (라) == — 견주기 전에 값의 종류를 바꿀 수 있어 갈래 확인에는 ===를 씁니다.

다시 볼 레슨: 빠짐없이 — never로 갈래 검사 · 작은 주문표 만들기

문제 10

차 한 잔의 값을 찍으려 합니다. 실행하면 3500이 찍히지만 검사에서 막힙니다.

interface Tea {
  kind: 'tea';
  won: number;
}
interface Cake {
  kind: 'cake';
  won: number;
  box: number;
}
type Item = Tea | Cake;

위 타입 선언에 이어 쓴 코드와 검사 결과입니다. 몇 번 줄의 무엇을 고쳐야 할까요? 고친 줄을 쓰세요.

function price(i: Item) {
  switch (i.kind) {
    case 'tea':
      return i.won;
    case 'cake':
      return i.won + i.box;
  }
}
const tea = {
  kind: 'tea',
  won: 3500,
};
console.log(price(tea));
검사 결과 오류 1개
main.ts(23,19): error TS2345: Argument of type '{ kind: string; won: number; }' is not assignable to parameter of type 'Item'.  Type '{ kind: string; won: number; }' is not assignable to type 'Tea'.    Types of property 'kind' are incompatible.      Type 'string' is not assignable to type '"tea"'.
힌트

들여 쓴 줄을 끝까지 읽고, 23번 줄에서 넘기는 tea의 타입이 몇 번 줄에서 정해졌는지 거슬러 갑니다.

힌트 둘

tea의 kind가 string이 된 줄을 찾습니다.

정답

19번 줄을 const tea: Item = {으로 고칩니다.

interface Tea {
  kind: 'tea';
  won: number;
}
interface Cake {
  kind: 'cake';
  won: number;
  box: number;
}
type Item = Tea | Cake;

고친 코드를 위 타입 선언에 이어 씁니다.

function price(i: Item) {
  switch (i.kind) {
    case 'tea':
      return i.won;
    case 'cake':
      return i.won + i.box;
  }
}
const tea: Item = {
  kind: 'tea',
  won: 3500,
};
console.log(price(tea));
실행 결과
3500

오류는 23번 줄에서 났지만 까닭은 19번 줄입니다. 타입을 적지 않고 만든 객체는 속성 kind가 'tea'에서 string으로 넓어집니다(검사 규칙 3). 들여 쓴 마지막 줄이 그 string을 Tea의 kind 자리 "tea"에 넣을 수 없다고 짚습니다. 만드는 줄에 Item을 적으면 kind가 그 자리에서 'tea'로 검사됩니다.

  • 다르게 고쳐도 되는 곳: const tea: Tea = {도 같습니다. 23번 줄에서 이름 tea 대신 객체를 price의 괄호 안에 바로 써서 넘겨도 지나갑니다.
  • 흔한 오답은 Tea의 kind를 kind: string;으로 바꾸는 것입니다. 그러면 갈래를 가르지 못해 16번 줄의 box가 막힙니다(TS2339). 11번 줄의 price 매개변수를 any로 바꾸면 오류는 사라지지만 검사를 끈 덮는 길입니다.

넓어진 타입이 생긴 줄 대신 오류가 난 줄에서 고칠 곳을 찾은 것입니다(오류 줄이 고칠 줄이라는 생각). 객체 속성의 'tea'도 값 그대로의 타입이라고 보면 이 오류가 왜 나는지 놓칩니다(넓어지지 않는다는 생각).

다시 볼 레슨: 판별 유니언 · 작은 주문표 만들기 · 타입 오류 읽기

문제 11 (만들어 보기)

교통 요금을 갈래마다 셈하는 함수 pay를 만들어 봅니다. 버스는 늘 1500원, 택시는 1km마다 1000원입니다. 다 채워 돌리면 이렇게 나와야 합니다.

1500
3000
interface Bus {
  kind: 'bus';
}
interface Taxi {
  kind: 'taxi';
  km: number;
}
type Ride = Bus | Taxi;

위 타입 선언에 이어 쓴 뼈대입니다. 주석 셋 아래를 채우세요. 검사 오류는 0이어야 하고, 갈래가 새로 생기면 검사기가 알려 주도록 '3. 나머지는 never로'도 채웁니다.

function pay(r: Ride) {
  // 1. 갈래 속성으로 나누기
  // 2. 갈래마다 처리
  // 3. 나머지는 never로
}
const rides: Ride[] = [
  { kind: 'bus' },
  { kind: 'taxi', km: 3 },
];
for (const r of rides) {
  console.log(pay(r));
}

다 쓰면 택시의 km를 5로 바꿔 따라가 봅니다. 둘째 줄이 5000이어야 합니다.

힌트

'1. 갈래 속성으로 나누기'는 switch (r.kind), '2. 갈래마다 처리'는 case 둘입니다. km는 택시 갈래 안에서만 쓸 수 있습니다.

정답

모범 답입니다. 타입 선언은 그대로 두고, 함수를 채운 뒤 뼈대의 아래 일곱 줄을 이어 붙입니다.

interface Bus {
  kind: 'bus';
}
interface Taxi {
  kind: 'taxi';
  km: number;
}
type Ride = Bus | Taxi;

채운 함수를 위 타입 선언에 이어 씁니다.

function pay(r: Ride) {
  // 1. 갈래 속성으로 나누기
  switch (r.kind) {
    // 2. 갈래마다 처리
    case 'bus':
      return 1500;
    case 'taxi':
      return r.km * 1000;
    // 3. 나머지는 never로
    default:
      const left: never = r;
      return left;
  }
}
검사 결과 오류 없음

뼈대의 아래 일곱 줄을 앞 코드에 이어 씁니다.

const rides: Ride[] = [
  { kind: 'bus' },
  { kind: 'taxi', km: 3 },
];
for (const r of rides) {
  console.log(pay(r));
}
실행 결과
1500
3000

km를 5로 바꾸면 택시 갈래가 5 * 1000을 돌려 둘째 줄이 5000입니다.

  • 다르게 써도 되는 곳: if (r.kind === 'bus')⁠로 버스를 먼저 돌려보내고 아래에서 택시를 셈해도 같은 화면이 나옵니다. 다만 그 꼴에는 never 줄이 없어, 갈래가 늘었을 때 검사기가 알려 주지 않습니다.
  • 검사 오류가 0이고, 찍은 화면이 위와 같고, km 5도 맞으면 맞습니다.
  • 흔한 오답은 case 'taxi'를 빠뜨린 것입니다. never 줄이 TS2322(Taxi를 never 자리에 넣을 수 없다)로 짚습니다. 그대로 돌리면 택시 객체가 default에서 그대로 돌아와 요금 대신 객체가 찍힙니다.

다시 볼 레슨: 빠짐없이 — never로 갈래 검사 · 판별 유니언

문제 12 (만들어 보기)

이번에는 빈 데서 씁니다. 음료 크기는 S · M · L 셋뿐이고 값은 차례로 3500 · 4000 · 4500원입니다. 크기 타입 Size와, 크기를 받아 값을 돌려주는 함수 price를 쓰세요. 갈래마다 처리하기 틀로 쓰고, price('M')을 찍으면 이렇게 나와야 합니다. 검사 오류는 0이어야 합니다.

4000

다 쓰면 price('L')로 바꿔 따라가 봅니다(4500). Size에 'XL'을 더하면 검사가 never 줄에서 막혀야 합니다.

힌트

하위 목표 셋(1. 갈래 속성으로 나누기 · 2. 갈래마다 처리 · 3. 나머지는 never로)을 따릅니다. 여기서는 크기 값 자체로 나눕니다.

정답

모범 답입니다.

type Size = 'S' | 'M' | 'L';
function price(s: Size) {
  switch (s) {
    case 'S':
      return 3500;
    case 'M':
      return 4000;
    case 'L':
      return 4500;
    default:
      const left: never = s;
      return left;
  }
}
console.log(price('M'));
실행 결과
4000

3번 줄이 '1. 갈래 속성으로 나누기', 4~9번 줄이 '2. 갈래마다 처리', 10~12번 줄이 '3. 나머지는 never로'입니다. price('L')이면 8번 줄로 가 4500입니다. Size에 'XL'을 더하면 처리하지 않은 'XL'이 default로 와서 11번 줄이 TS2322('XL'을 never 자리에 넣을 수 없다)로 막힙니다.

  • 다르게 써도 되는 곳: case 차례는 바꿔도 됩니다. 매개변수 이름은 달라도 됩니다.
  • 검사 오류가 0이고, 찍은 화면이 위와 같고, price('L')도 4500이면 맞습니다.
  • 흔한 오답은 default와 never 줄을 빼고 쓴 것입니다. S · M · L만 넘길 때는 같은 값이 나오지만, 'XL'을 더해도 검사는 조용하고 price('XL')이 undefined를 돌려줍니다. default를 두지 않아도 새 갈래가 빠지면 어디선가 검사 오류가 난다고 본 것입니다. 새 갈래를 알려 주는 것은 never 줄입니다.

다시 볼 레슨: 빠짐없이 — never로 갈래 검사

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

타입스크립트 목차로