4단원 '유니언과 좁히기'의 다섯 레슨에서 아홉 문제를 내고, 앞 단원(1 · 3단원)에서 세 문제를 섞었습니다. 앞 단원 문제가 어느 레슨인지는 정답을 펼치면 보입니다. 검사 결과는 지나가는지와 막히는 줄을, 지금 타입은 검사기가 찍는 글자를, 실행 결과는 찍히는 글자를 종이에 끝까지 쓴 뒤 정답을 펼치세요. 실행이 오류로 멈추면 오류 이름이 든 줄까지만 써도 됩니다. 막히면 정답 앞의 힌트를 먼저 펼칩니다. 점수는 매기지 않습니다.
한 번에 다 풀 필요는 없습니다. 문제 1~10은 폰으로도 풀 수 있고(약 30~40분), 끝의 만들어 보기 둘은 컴퓨터에서 tsc와 Node로 직접 검사하고 돌려 봅니다. 이 단원을 건너뛸지 보려면 진단 문항인 문제 1 · 2 · 4 · 5 · 7 다섯을 먼저 풀어 보세요(약 15분). 진단 문항은 힌트를 펴지 않고 풉니다. 힌트를 펴고 맞힌 문제나 짐작으로 맞힌 문제는 틀린 것으로 칩니다. 모두 맞으면 이 단원 레슨은 건너뛰고, 틀린 문제가 있으면 그 정답 끝의 레슨만 봅니다. 끝의 만들어 보기 둘은 골라서 풉니다.
지금 타입을 묻는 문제는 줄마다 이름의 타입을 적는 타입 추적 표를 그려 풀어도 됩니다. 끝의 두 문제는 함수를 직접 만들어 봅니다. 컴퓨터가 있으면 main.ts로 저장해 npx tsc로 검사 오류가 0인지 본 뒤 node main.ts로 돌려 맞춰 봅니다. 플레이그라운드에서는 맨 위에 export {}; 한 줄을 더합니다. 폰으로만 본다면 문제에 적힌 값으로 내 코드를 한 줄씩 검사하고 따라가 나와야 할 화면과 맞춰 봅니다. 검사 결과는 tsc 7.0.2로, 실행 결과는 Node 26으로 돌린 것입니다.
문제 1
사물함 번호는 수(12)일 수도 있고 글자('B7')일 수도 있습니다. 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.
function locker(
v: string | number,
) {
const a = v.toString();
const b = v.toUpperCase();
const c = v.toFixed(0);
return a;
}
힌트
4~6번 줄의 메서드마다 string과 number 가운데 어느 쪽에 있는지 따져 봅니다.
정답
main.ts(5,15): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.main.ts(6,15): error TS2339: Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.5 · 6번 줄에서 막힙니다. string | number인 값에는 두 타입이 함께 가진 것만 쓸 수 있습니다.
- 4번 줄: toString은 글자에도 수에도 있어 지나갑니다.
- 5번 줄: toUpperCase는 글자에만 있습니다. 들여 쓴 둘째 줄이 'number에는 없다'고 짚습니다.
- 6번 줄: toFixed는 수에만 있습니다. 둘째 줄이 'string에는 없다'고 짚습니다.
흔한 오답은 '지나감'입니다. 유니언 값에는 두 타입 가운데 아무 쪽의 메서드나 쓸 수 있다고 본 것입니다. v가 실제로 어느 쪽일지 검사기는 모르니, 두 쪽 모두에 있는 것만 지나갑니다(검사 규칙 2). 4번 줄까지 막힌다고 쓰면 유니언에는 어떤 메서드도 못 쓴다고 본 것입니다.
다시 볼 레슨: 유니언 — 여럿 가운데 하나
문제 2
등산 코스의 난이도는 easy와 hard 둘뿐입니다. 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.
type Level = 'easy' | 'hard';
let lv: Level = 'easy';
lv = 'normal';
if (lv === 'expert') {
console.log('장비 챙기기');
}
힌트
1번 줄의 Level을 먼저 읽고, 3번 줄과 4번 줄을 하나씩 봅니다.
힌트 둘
3번 줄과 4번 줄에 나오는 글자가 Level에 드는 값인지 봅니다.
정답
main.ts(3,1): error TS2322: Type '"normal"' is not assignable to type 'Level'.main.ts(4,5): error TS2367: This comparison appears to be unintentional because the types 'Level' and '"expert"' have no overlap.3 · 4번 줄에서 막힙니다.
- 3번 줄: Level에 드는 값은 'easy'와 'hard' 둘뿐이라 'normal'을 넣을 수 없습니다. 검사기는 리터럴 타입을 큰따옴표로 찍습니다(
"normal"). - 4번 줄: lv는 Level의 값만 가질 수 있는데 'expert'와 견줍니다. TS2367은 '두 타입이 겹치지 않아 이 비교는 실수로 보인다', 곧 이 조건은 늘 거짓이라는 뜻입니다.
흔한 오답은 3번 줄만 쓴 것입니다. Level에 없는 값과 견주면 검사기가 아무 말 없이 거짓으로 둔다고 본 것입니다. 검사기는 값 몇 개로 된 타입의 견주기도 읽어, 늘 거짓인 조건을 짚습니다.
다시 볼 레슨: 리터럴 유니언으로 값 고르기
문제 3
컵 크기는 S와 L 둘뿐입니다. 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.
type Size = 'S' | 'L';
interface Cup {
size: Size;
}
function put(c: Cup) {
return '컵 ' + c.size;
}
const mine = { size: 'L' };
console.log(put(mine));
console.log(put({ size: 'L' }));
힌트
8번 줄에서 mine을 만들 때 size 속성의 타입이 무엇으로 정해지는지, 10번 줄의 객체는 어디서 만들었는지 봅니다.
정답
main.ts(9,17): error TS2345: Argument of type '{ size: string; }' is not assignable to parameter of type 'Cup'. Types of property 'size' are incompatible. Type 'string' is not assignable to type 'Size'.9번 줄에서만 막힙니다.
- 8번 줄: mine은 Cup이라고 적지 않은 객체라, 속성 size의 타입이 'L'에서 넓어져 string이 됩니다(검사 규칙 3). 들여 쓴 줄이 까닭을 짚습니다 — size 속성의 string은 Size에 넣을 수 없습니다.
- 10번 줄: Cup 자리에 객체를 바로 써서 넘겼습니다. 그 자리에서 size의 'L'을 Size로 검사하니 지나갑니다.
흔한 오답은 '지나감'입니다. 객체 속성에 둔 'L'도 타입이 "L"이라고 본 것입니다(넓어지지 않는다는 생각). 10번 줄까지 막힌다고 쓰면 바로 쓴 객체도 먼저 넓어진다고 본 것입니다. 8번 줄에 const mine: Cup =처럼 Cup을 적으면 9번 줄도 지나갑니다.
다시 볼 레슨: 리터럴 유니언으로 값 고르기 · 적지 않아도 붙는 타입 — 추론 · 객체 타입과 interface
문제 4
쿠폰 코드는 글자 · 수 · null 가운데 하나입니다. 10번 줄에서 v의 지금 타입은 무엇일까요? 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.
function code(
v: string | number | null,
) {
if (v === null) {
return '없음';
}
if (typeof v === 'number') {
console.log('번호 ' + v);
}
return v.toUpperCase();
}
힌트
타입 추적 표를 그립니다. v를 확인하거나 읽는 줄(4 · 7 · 8 · 10)마다 한 줄씩 적습니다.
힌트 둘
4번 줄과 7번 줄의 확인 뒤 갈래가 각각 return으로 끝나는지 봅니다.
정답
10번 줄에서 v의 지금 타입은 string | number이고, 검사는 10번 줄에서 막힙니다.
main.ts(10,12): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.타입 추적 표입니다.
| 줄 | 이름 | 지금 타입 |
|---|---|---|
| 2 | v | string | number | null |
| 4 | v | string | number | null → 참 null 거짓 string | number |
| 7 | v | 만나는 줄 — 참은 오지 않음(5번 줄 return) + 거짓 string | number → string | number |
| 7 | v | string | number → 참 number 거짓 string |
| 8 | v | number |
| 10 | v | 만나는 줄 — 참 number + 거짓 string → string | number |
4번 줄의 참 길은 5번 줄 return으로 끝나 뒤로 오지 않습니다. 그래서 7번 줄부터 null은 빠집니다. 7번 줄의 참 길은 return 없이 8번 줄을 지나 10번 줄로 와, 10번 줄에서 number와 string이 합쳐집니다(검사 규칙 5). number에는 toUpperCase가 없어 막힙니다.
같은 답으로 치는 꼴: number | string처럼 차례만 바꿔 쓴 것.
흔한 오답은 셋입니다.
string— 7번 줄 갈래의 number가 10번 줄에 오지 않는다고 본 것입니다. return으로 끝난 길만 오지 않습니다(좁힘이 끝까지 간다는 생각).string | number | null— 두 확인을 지나 10번 줄에 오면 v가 2번 줄에 적은 타입 그대로라고 본 것입니다(갈래를 나오면 선언 타입이라는 생각). 만나는 줄은 오는 길들의 타입만 합칩니다.- '지나감' — 실제로 넘어오는 값이 글자면 문제없다고 본 것입니다(검사기가 실행을 따라간다는 생각). 검사기는 값을 모르니 두 갈래를 모두 읽습니다.
다시 볼 레슨: 좁히기 — typeof와 타입 추적 표 · null·undefined 거르기
문제 5
음료 메모는 없을 수도 있습니다. 2 · 3번 줄을 읽은 뒤 a와 b의 지금 타입은 무엇일까요? 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.
function check(memo?: string) {
const a = memo?.length;
const b = memo ?? '없음';
const c = a + 1;
const d = b.length + 1;
console.log(c, d);
}
힌트
memo가 undefined일 때 2번 줄과 3번 줄이 각각 무엇을 주는지 따라가 봅니다.
정답
a는 number | undefined, b는 string이고, 검사는 4번 줄에서 막힙니다.
main.ts(4,13): error TS18048: 'a' is possibly 'undefined'.- 2번 줄: memo가 undefined면
?.는 length를 꺼내지 않고 undefined를 줍니다. 그래서 a는 length의 number에 undefined가 더해집니다. - 3번 줄: memo가 undefined면
??가 '없음'을 줍니다. 어느 길이든 글자라 b는 string입니다. - 4번 줄: undefined일 수 있는 a에 1을 더해 TS18048로 막힙니다. 5번 줄의 b.length는 지나갑니다.
같은 답으로 치는 꼴: a를 undefined | number처럼 차례만 바꿔 쓴 것.
흔한 오답은 a를 number로 쓰고 '지나감'이라고 쓴 것입니다. ?. 연산자로 꺼낸 값도 원래 속성의 타입 그대로라고 본 것입니다(꺼낸 값은 늘 있다는 생각). 5번 줄도 막힌다고 쓰면 ?? 뒤에도 undefined가 남는다고 본 것입니다.
JS 다시 보기: ?.는 왼쪽이 null·undefined이면 더 꺼내지 않고 undefined를 줍니다(JS '객체를 담은 배열').
다시 볼 레슨: null·undefined 거르기
문제 6
영화관의 줄마다 남은 자리 수를 모았습니다. 5번 줄에서 b의 지금 타입은 무엇일까요? Node로 돌리면 무엇이 찍힐까요?
interface Seats {
[row: string]: number;
}
const seats: Seats = { A: 4 };
const b = seats['B'];
const left = b ?? 0;
console.log(b, left);
힌트
1~3번 줄의 Seats가 칸의 값에 대해 무엇을 말하는지, 실행 때 'B' 칸이 정말 있는지를 따로 봅니다.
정답
b의 지금 타입은 number입니다.
undefined 0- 5번 줄: 인덱스 시그니처는 '어떤 이름의 칸이든 값은 number'라는 뜻이라, 검사기는 'B' 칸이 있는지 모른 채 number로 봅니다. 검사 오류도 없습니다.
- 실행: seats에는 A 칸뿐이라
seats['B']는 undefined입니다. 6번 줄의??가 undefined를 보고 0을 줍니다.
흔한 오답은 b를 찍는 첫 값을 수로 쓴 것입니다. 지금 타입이 number이니 값도 늘 있다고 본 것입니다(꺼낸 값은 늘 있다는 생각). 검사기의 number는 '있다면 수'라는 뜻일 뿐입니다. b를 number | undefined로 쓰면 검사기가 없는 칸을 안다고 본 것입니다 — 이 과목 설정에서는 더해지지 않습니다(설정을 바꾸는 법은 10단원). 그래서 없을 수 있는 칸은 6번 줄처럼 실행할 때 거릅니다.
다시 볼 레슨: 키가 정해지지 않은 객체 — 인덱스 시그니처 · null·undefined 거르기
문제 7
잔 수를 찍은 뒤 '잔'을 붙여 두는 반복입니다. 3번 줄에서 cups의 지금 타입은 무엇일까요? 검사 결과와 실행 결과를 각각 쓰세요.
let cups: number | string = 2;
for (let i = 0; i < 2; i++) {
console.log(cups.toFixed(0));
cups = cups + '잔';
}
힌트
타입 추적 표에 1 · 3 · 4번 줄의 cups를 적어 봅니다.
힌트 둘
3번 줄로 오는 길은 둘입니다 — 반복 앞에서 처음 들어오는 길과 4번 줄 뒤 덩어리 끝에서 돌아오는 길.
정답
3번 줄에서 cups의 지금 타입은 string | number입니다.
main.ts(3,20): error TS2339: Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.2main.ts:3 console.log(cups.toFixed(0)); ^ TypeError: cups.toFixed is not a function at main.ts:3:20| 줄 | 이름 | 지금 타입 |
|---|---|---|
| 1 | cups | number |
| 4 | cups | string |
| 3 | cups | 반복 머리 — 처음 number + 돌아온 길 string → string | number |
1번 줄 대입 뒤 cups는 number이고, 4번 줄에서 글자에 다시 붙은 뒤에는 string입니다. 반복 머리에서 두 길이 합쳐져 3번 줄의 cups는 string | number이고, string에는 toFixed가 없어 막힙니다(검사 규칙 5).
Node는 첫 바퀴에 2를 찍고, cups가 '2잔'에 붙은 둘째 바퀴의 3번 줄에서 멈춥니다.
같은 답으로 치는 꼴: number | string처럼 차례만 바꿔 쓴 것.
흔한 오답은 지금 타입을 number로 쓰고 '지나감'이라고 쓴 것입니다. 검사기가 첫 바퀴만 따라간다고 본 것입니다(검사기가 실행을 따라간다는 생각). 실행 결과를 2와 '2잔' 두 줄로 쓰면 둘째 바퀴의 cups가 글자라는 것을 놓친 것입니다.
다시 볼 레슨: 좁힘이 달라지는 곳 — 대입과 반복
문제 8
하고 싶은 일마다 쓸 기호나 낱말을 쓰세요.
- (가) 둘 가운데 하나인 타입을 적을 때 두 타입 사이에 쓰는 기호
- (나) 값의 종류를 글자로 돌려주어, if 안에서 이름을 좁히는 데 쓰는 연산자
- (다) 왼쪽이 null · undefined면 더 꺼내지 않고 undefined를 주는 꺼내기 기호
- (라) 왼쪽이 null · undefined일 때만 오른쪽 값을 쓰는 연산자
힌트
(다)와 (라)는 둘 다 물음표로 시작합니다.
정답
받는 답: (가) | · (나) typeof · (다) ?. · (라) ??
틀리는 꼴:
- (가)
||— 값을 고르는 자바스크립트 연산자라 타입 자리에는 쓰지 않습니다. - (다)
!— 검사기에게 'null · undefined가 아니다'라고 말할 뿐 확인하지 않습니다. 실행 때 없는 값이면 그대로 멈춥니다. - (라)
||— 왼쪽이 0 ·''처럼 거짓 같은 값이어도 오른쪽을 씁니다.
다시 볼 레슨: 유니언 — 여럿 가운데 하나 · 좁히기 — typeof와 타입 추적 표 · null·undefined 거르기
문제 9
컵 크기를 구조 분해로 받는 함수입니다. 검사를 지나갈까요? 지나가지 못하면 막히는 줄을 모두 쓰세요.
type Size = 'S' | 'L';
function fit({ size: Size }) {
return size + ' 사이즈';
}
console.log(fit({ size: 'S' }));
힌트
2번 줄 중괄호 안의 콜론이 하는 일을 떠올리고, 3번 줄의 size가 어디서 생기는지 봅니다.
정답
main.ts(2,22): error TS7031: Binding element 'Size' implicitly has an 'any' type.main.ts(3,10): error TS2552: Cannot find name 'size'. Did you mean 'Size'?2 · 3번 줄에서 막힙니다. 구조 분해 안의 콜론은 이름을 바꿔 붙이는 표시라, 2번 줄은 size 속성을 꺼내 Size라는 이름을 붙입니다. 그 이름의 타입은 정해지지 않았고(TS7031), size라는 이름은 생기지 않아 3번 줄이 막힙니다(TS2552 — 그런 이름이 없다, 짐작은 Size). Node로 돌려도 3번 줄에서 ReferenceError로 멈춥니다.
타입은 구조 분해 중괄호 뒤에 적습니다.
type Size = 'S' | 'L';
interface Cup {
size: Size;
}
function fit({
size,
}: Cup) {
return size + ' 사이즈';
}
const c: Cup = { size: 'S' };
console.log(fit(c));S 사이즈흔한 오답은 '지나감'입니다. 구조 분해 안의 콜론 뒤가 속성의 타입이라고 본 것입니다(구조 분해에 타입을 붙이는 생각).
다시 볼 레슨: 객체 타입과 interface
문제 10
오늘 주문 목록은 아직 없을 수도 있습니다(null). 주문 수를 세어 1과 0을 찍으려 합니다. 검사에서 이렇게 막혔습니다. 몇 번 줄의 무엇을 고쳐야 할까요? 고친 줄을 쓰세요.
function count(
v: string[] | null,
) {
if (typeof v === 'object') {
return v.length;
}
return 0;
}
console.log(count(['라테']));
console.log(count(null));
main.ts(5,12): error TS18047: 'v' is possibly 'null'.힌트
4번 줄의 확인이 참일 때 v가 무엇일 수 있는지 봅니다.
힌트 둘
typeof null이 내는 글자를 떠올립니다.
정답
4번 줄을 if (v !== null) {로 고칩니다.
function count(
v: string[] | null,
) {
if (v !== null) {
return v.length;
}
return 0;
}
console.log(count(['라테']));
console.log(count(null));1
0오류는 5번 줄에서 났지만 까닭은 4번 줄의 확인입니다. typeof null은 'object'라, typeof v === 'object'가 참인 갈래에도 null이 남습니다. 그래서 5번 줄의 v는 string[] | null이고 TS18047(null일 수 있다)로 막힙니다. null을 직접 거르면 5번 줄의 v가 string[]으로 좁혀집니다.
- 다르게 고쳐도 되는 곳: 4번 줄을
if (v) {로 써도 null이 걸러져 1과 0을 찍습니다. - 흔한 오답은 오류가 난 5번 줄만 고치는 것입니다.
return v!.length;는 검사기에게 null이 아니라고 말해 검사 오류를 지우지만, 실행하면count(null)에서TypeError로 멈춥니다.!는 확인이 아니라 말일 뿐입니다. return v?.length;로 고쳐도 검사는 지나지만 둘째 줄이 0이 아니라 undefined입니다.
typeof가 'object'면 null이 아니라고 본 것입니다(object면 null이 아니라는 생각). 오류가 난 줄만 덮으면 검사는 지나도 결과는 틀립니다(오류 줄이 고칠 줄이라는 생각).
JS 다시 보기: typeof null은 'object'입니다(JS '수·글자·불리언과 typeof').
다시 볼 레슨: 좁히기 — typeof와 타입 추적 표 · null·undefined 거르기
문제 11 (만들어 보기)
할인 금액 계산기를 만들어 봅니다. bill은 값과 할인 금액을 받아 낼 돈을 돌려주고, 할인이 없으면 값 그대로 돌려줍니다. 아래 뼈대로 돌리면 이렇게 나와야 합니다.
4000
3500
주석 '1. 할인이 있으면 빼기'와 '2. 없으면 그대로' 아래를 채우세요. 검사 오류는 0이어야 합니다.
function bill(
price: number,
off?: number,
) {
// 1. 할인이 있으면 빼기
// 2. 없으면 그대로
}
console.log(bill(4500, 500));
console.log(bill(3500));
다 쓰면 마지막 줄을 bill(2000, 300)으로 바꿔 따라가 봅니다. 1700이 나와야 합니다.
힌트
'1. 할인이 있으면 빼기'는 if 갈래 하나, '2. 없으면 그대로'는 그 아래 return 한 줄입니다.
정답
모범 답입니다.
function bill(
price: number,
off?: number,
) {
// 1. 할인이 있으면 빼기
if (off !== undefined) {
return price - off;
}
// 2. 없으면 그대로
return price;
}
console.log(bill(4500, 500));
console.log(bill(3500));4000
3500bill(2000, 300)이면 off가 300이라 갈래로 들어가 1700입니다. 갈래 안에서 off의 지금 타입은 undefined가 빠진 number라 빼기가 지나갑니다.
- 다르게 써도 되는 곳: 조건을
if (off)로 써도 이 값들에서는 같습니다(할인 0이면 갈래로 들어가지 않지만 결과는 같음). 갈래 없이return price - (off ?? 0);한 줄로 써도 됩니다. - 검사 오류가 0이고, 찍은 화면이 위와 같고, 1700도 맞으면 맞습니다.
- 흔한 오답은 거르지 않고
return price - off;만 쓴 것입니다. 검사는 TS18048(off가 undefined일 수 있다)로 막히고, 그대로 돌리면bill(3500)이 NaN을 찍습니다(꺼낸 값은 늘 있다는 생각).
function bill(
price: number,
off?: number,
) {
return price - off;
}
console.log(bill(4500, 500));
console.log(bill(3500));main.ts(5,18): error TS18048: 'off' is possibly 'undefined'.4000
NaN다시 볼 레슨: null·undefined 거르기 · 선택·기본값 매개변수
문제 12 (만들어 보기)
이번에는 빈 데서 씁니다. 택배 무게는 수(kg)로 오기도 하고 '모름' 같은 글자로 오기도 합니다. 무게 하나를 받아, 수면 소수 한 자리에 kg을 붙이고 글자면 앞에 '무게 '를 붙여 돌려주는 함수 weight를 쓰세요. weight(2.5)와 weight('모름')을 찍으면 이렇게 나와야 하고, 검사 오류는 0이어야 합니다.
2.5kg
무게 모름
다 쓰면 weight(2.5)를 weight(3)으로 바꿔 따라가 봅니다. 3.0kg이 나와야 합니다.
힌트
하위 목표 셋의 주석(1. 수인지 확인하기 · 2. 수면 kg 붙이기 · 3. 글자면 그대로)을 먼저 쓰고, 그 아래를 채웁니다. 매개변수의 타입은 둘 가운데 하나입니다.
정답
모범 답입니다.
function weight(
v: number | string,
) {
if (
typeof v === 'number'
) {
const kg = v.toFixed(1);
return kg + 'kg';
}
return '무게 ' + v;
}
console.log(weight(2.5));
console.log(weight('모름'));2.5kg
무게 모름4~6번 줄이 '1. 수인지 확인하기', 7 · 8번 줄이 '2. 수면 kg 붙이기', 10번 줄이 '3. 글자면 그대로'입니다. 정답 접기 안은 폭이 좁아 조건을 괄호 안에서 나눴습니다 — 한 줄로 if (typeof v === 'number') {라고 써도 같습니다. weight(3)이면 v가 수라 갈래로 들어가 toFixed(1)이 '3.0'을 만들어 3.0kg입니다.
- 다르게 써도 되는 곳: 매개변수를
string | number로 적어도 같습니다.typeof v === 'string'을 먼저 확인하고 수를 아래에 둬도 됩니다. - 검사 오류가 0이고, 찍은 화면이 위와 같고,
weight(3)도3.0kg이면 맞습니다. - 흔한 오답 하나는 확인 없이
v.toFixed(1)을 쓴 것입니다.number | string에는 toFixed가 함께 있지 않아 TS2339로 막힙니다. 좁힌 갈래 안에서만 수의 메서드를 씁니다. - 다른 흔한 오답은 수 갈래에서
v + 'kg'처럼 toFixed 없이 붙인 것입니다. 검사는 지나고 2.5에서는 맞지만,weight(3)이3kg을 찍어 둘째 시험 값에서 갈립니다.
다시 볼 레슨: 좁히기 — typeof와 타입 추적 표 · 유니언 — 여럿 가운데 하나