리터럴 타입(literal type)은 값 하나로 된 타입입니다. 'tall' | 'grande'처럼 리터럴 타입을 유니언으로 묶으면 그 값 몇 개만 든 모음이 되고, 검사기는 그 밖의 값을 막습니다.
카페의 사이즈는 tall과 grande 둘뿐입니다. 사이즈를 string으로 적으면 오타 'tal'도, 없는 사이즈 'short'도 그 자리에 들어갑니다. string 모음이 고를 값보다 훨씬 크기 때문입니다. 값 몇 개로 된 타입을 적으면 그런 값을 실행하기 전에 찾습니다.
1값 몇 개로 된 타입
'tall' | 'grande'는 값 'tall' 하나짜리 모음과 'grande' 하나짜리 모음을 합친 타입입니다. 이 타입으로 적은 이름을 두 값 밖의 글자에 다시 붙이면 막힙니다.
let size: 'tall' | 'grande' =
'tall';
size = 'short';
main.ts(3,1): error TS2322: Type '"short"' is not assignable to type '"grande" | "tall"'.'"short" 타입은 "grande" | "tall" 타입에 넣을 수 없다', 곧 두 값만 고를 수 있는 자리에 그 밖의 글자를 넣었다는 뜻입니다. 찍힌 차례가 적은 차례와 달라도 같은 타입입니다(앞 레슨).
'tall'처럼 값 하나로 된 타입이 리터럴 타입입니다. 적지 않아도 붙는 타입 — 추론에서 const cup = 'tall';의 타입이 "tall"이었던 그것입니다. 코드에는 작은따옴표로 쓰고, 검사기는 같은 값을 큰따옴표로 찍습니다.
같은 유니언을 여러 곳에 쓰면 type으로 이름을 붙입니다. 함수 타입과 콜백에서 함수 타입에 이름을 붙인 것과 같은 꼴입니다.
type Size = 'tall' | 'grande';
function order(size: Size) {
return '라테 ' + size;
}
console.log(order('tall'));
console.log(order('short'));
main.ts(6,19): error TS2345: Argument of type '"short"' is not assignable to parameter of type 'Size'.이 TS2345도 넘긴 인자의 타입이 매개변수 모음 밖이라는 뜻입니다. 이름을 붙인 타입은 오류 문장에도 이름 'Size'로 찍힙니다.
2let과 객체 속성은 string으로 넓어진다
이번에는 사이즈 값에 이름 mine을 먼저 붙이고, 그 이름을 넘깁니다. 5번 줄의 값은 'tall'이라 Size 모음에 드는 값입니다. 6번 줄은 검사를 지나갈까요? 답을 정한 뒤 펼쳐 보세요.
type Size = 'tall' | 'grande';
function order(size: Size) {
return '라테 ' + size;
}
let mine = 'tall';
console.log(order(mine));
검사 결과 보기
main.ts(6,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Size'.예상과 다르면 그곳이 배울 곳입니다.
6번 줄에서 막힙니다. 넘긴 값은 'tall'이지만, 오류 문장은 그 인자의 타입을 'string'이라고 합니다.
let으로 만든 이름은 나중에 다른 글자에 다시 붙일 수 있습니다. 그래서 검사기는 첫 값 'tall'에서 mine의 선언 타입을 string으로 넓혀 정합니다(검사 규칙 3). string 모음은 Size 모음보다 커서 Size 자리에 들지 않습니다.
const로 만든 이름은 다른 값에 다시 붙일 수 없습니다. 그래서 글자 하나로 만든 const 이름은 선언 타입이 그 값 하나 "tall"이고, 7번 줄은 지나갑니다. 객체 속성은 다릅니다 — 8번 줄은 막힙니다.
type Size = 'tall' | 'grande';
function order(size: Size) {
return '라테 ' + size;
}
const mine = 'tall';
const cup = { size: 'tall' };
console.log(order(mine));
console.log(order(cup.size));
main.ts(8,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Size'.cup은 const로 만들었지만, 객체의 속성 값은 나중에 바꿀 수 있습니다(cup.size = 'venti';). 그래서 검사기는 속성의 타입도 string으로 넓힙니다(검사 규칙 3). const가 막는 것은 이름 cup을 다른 객체에 다시 붙이는 일뿐입니다.
3고치는 길 둘 — const와 타입 적기
Size 자리에 넘길 값이 string으로 넓어지지 않게 하는 길이 둘입니다. 하나는 2절처럼 글자 하나를 const로 만드는 것입니다. 다른 하나는 선언할 때 타입을 Size로 적는 것입니다.
type Size = 'tall' | 'grande';
interface Cup {
size: Size;
}
function order(size: Size) {
return '라테 ' + size;
}
let mine: Size = 'grande';
const cup: Cup = {
size: 'tall',
};
console.log(order(mine));
console.log(order(cup.size));
라테 grande
라테 tall8번 줄의 mine은 let이어도 선언 타입이 Size입니다. 그래서 나중에 다시 붙일 값도 Size 안에서만 고릅니다 — mine = 'tal';이라고 쓰면 TS2820으로 막히고, 오류 문장 끝에 Did you mean '"tall"'?이 붙습니다. 객체는 객체 타입과 interface에서처럼 속성 줄을 interface로 적고, 그 interface를 cup의 타입으로 적습니다.
값에서 타입을 뽑아 쓰는 as const라는 길도 있습니다. 이것은 8단원 '값에서 타입 뽑기 — typeof와 as const'에서 배웁니다.
남의 코드에서 만나는 enum은 값도 만드는 타입스크립트 문법이라 Node가 바로 돌리지 못해, 이 과목은 리터럴 유니언을 씁니다.
예제
사이즈에 따라 값 더하기
grande는 500원을 더 받습니다. size가 Size라서 이 함수에는 tall과 grande만 넘어옵니다.
type Size = 'tall' | 'grande';
function cost(size: Size) {
if (size === 'grande') {
return 4500 + 500;
}
return 4500;
}
console.log(cost('tall'));
console.log(cost('grande'));
4500
5000부르는 쪽에서 대문자로 cost('Tall')이라고 쓰면 TS2345로 막힙니다. 자바스크립트였다면 'Tall' === 'grande'가 거짓이라 오류 없이 4500을 돌려줬을 오타입니다.
자주 틀리는 곳
틀린 생각: let이나 객체 속성에 둔 'tall'도 타입이 "tall"이다.
지금 든 값이 'tall' 하나뿐이니 타입도 그 값이라고 여기기 쉽습니다. 검사기는 지금 값만이 아니라, 그 이름이나 속성이 앞으로 가리킬 수 있는 값까지 생각해 선언 타입을 정합니다. 2절의 let mine과 cup.size가 그랬습니다.
맞는 규칙: let 이름과 객체 속성은 나중에 바뀔 수 있어서, 첫 값이 글자면 선언 타입이 string으로 넓어집니다. 리터럴 타입이 필요하면 글자 하나로 만든 이름을 const로 두거나 타입을 적습니다(검사 규칙 3).
틀린 생각: TS2345가 난 줄, 곧 부르는 줄을 고쳐야 한다.
오류 자리가 main.ts(6,19)처럼 부르는 줄을 가리키니, 그 줄에 고칠 것이 있다고 여기기 쉽습니다. 2절 코드에서 오류가 난 곳은 6번 줄이지만, mine이 string이 된 까닭은 5번 줄 let mine = 'tall';에 있습니다.
맞는 규칙: 오류는 검사기가 발견한 곳입니다. 넘긴 값의 타입이 정해진 줄로 거슬러 가서, 그 줄을 const로 바꾸거나 타입을 적어 고칩니다.
틀린 생각: Size에 없는 값과 견주면 검사기는 아무 말 없이 거짓으로 둔다.
자바스크립트에서는 size === 'short'가 오류 없이 false가 됩니다. 검사기는 그 비교가 결코 참이 될 수 없다는 것을 압니다.
type Size = 'tall' | 'grande';
function cost(size: Size) {
if (size === 'short') {
return 4000;
}
return 4500;
}
main.ts(3,7): error TS2367: This comparison appears to be unintentional because the types 'Size' and '"short"' have no overlap.'이 비교는 뜻한 것이 아닌 것 같다 — Size와 "short"는 겹치는 값이 없다(have no overlap)'는 뜻입니다. 3번 줄의 갈래는 어떤 값으로 불러도 들어가지 않습니다.
맞는 규칙: 검사기는 겹치는 값이 없는 두 타입을 견주는 비교를 잡아 알려 줍니다. 오타이거나 없는 값을 견준 것이니 값이나 타입을 고칩니다.
확인 문제
1. 예제 코드에서 마지막 두 줄을 바꿨습니다. 이 코드는 검사를 지나갈까요? 지나가지 못하면 몇 번 줄에서 막힐까요?
type Size = 'tall' | 'grande';
function cost(size: Size) {
if (size === 'grande') {
return 4500 + 500;
}
return 4500;
}
let pick = 'grande';
console.log(cost(pick));
- ① 지나간다.
- ② 8번 줄에서 막힌다.
- ③ 9번 줄에서 막힌다.
힌트
8번 줄이 let인지 const인지 보고, pick의 선언 타입을 먼저 적어 봅니다.
정답
main.ts(9,18): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Size'.③입니다. let으로 만든 pick은 선언 타입이 string으로 넓어져, 9번 줄에서 Size 자리에 넘기는 인자가 막힙니다. 고칠 곳은 8번 줄입니다 — const pick = 'grande';나 let pick: Size = 'grande';.
- ① 값이
'grande'이니 타입도"grande"라고 본 것입니다(넓어지지 않는다는 생각).let은 string으로 넓힙니다. - ② 오류가 고칠 줄에서 난다고 본 것입니다(오류 줄이 고칠 줄이라는 생각). 8번 줄 자체는 이름
pick을 글자에 붙이는 맞는 줄이고, 검사기는 넘기는 9번 줄에서 발견합니다.
2. 음료 온도는 'hot'과 'iced' 둘뿐입니다. 5~6번 줄은 지나가고 7번 줄만 막히도록 빈칸에 타입을 쓰세요.
type Temp = ________;
function drink(temp: Temp) {
return '라테 ' + temp;
}
console.log(drink('hot'));
console.log(drink('iced'));
console.log(drink('warm'));
힌트
고를 수 있는 값 둘을 리터럴 타입으로 적고 |로 묶습니다.
정답
'hot' | 'iced'입니다.
type Temp = 'hot' | 'iced';
function drink(temp: Temp) {
return '라테 ' + temp;
}
console.log(drink('hot'));
console.log(drink('iced'));
console.log(drink('warm'));main.ts(7,19): error TS2345: Argument of type '"warm"' is not assignable to parameter of type 'Temp'.다르게 써도 되는 곳: 'iced' | 'hot'으로 차례를 바꿔 써도 같은 타입입니다.
흔한 오답은 string입니다. 그러면 'warm'도 string 모음에 들어 7번 줄까지 지나갑니다.
3. 장보기 목록에서 찻잎의 무게 단위를 찍는 코드입니다. 6번 줄이 막힙니다. 이 오류를 고치는 길로 가장 알맞은 것을 고르세요.
type Unit = 'kg' | 'g';
function weigh(unit: Unit) {
return '단위 ' + unit;
}
const tea = { unit: 'g' };
console.log(weigh(tea.unit));
main.ts(6,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Unit'.- ① 6번 줄을
console.log(weigh('g'));로 바꾼다. - ② 1번 줄을
type Unit = string;으로 바꾼다. - ③
interface Item { unit: Unit; }를 두고 5번 줄을const tea: Item = { unit: 'g' };처럼 적는다.
힌트
tea.unit이 왜 string이 됐는지 5번 줄에서 찾습니다. 고친 뒤에도 'kgg' 같은 오타를 검사기가 잡는지 따져 봅니다.
정답
③입니다. 객체 속성은 string으로 넓어지니, 객체에 타입을 적어 unit 속성의 선언 타입을 Unit으로 정합니다.
type Unit = 'kg' | 'g';
interface Item {
unit: Unit;
}
function weigh(unit: Unit) {
return '단위 ' + unit;
}
const tea: Item = {
unit: 'g',
};
console.log(weigh(tea.unit));단위 g- ① 오류가 난 줄을 고친 것입니다(오류 줄이 고칠 줄이라는 생각). 검사는 지나지만
tea의 단위를 쓰지 않게 되어,tea의 단위가'kg'로 바뀌어도 g가 찍힙니다. - ② 오류는 사라지지만 Unit이 다시 큰 string 모음이 되어,
weigh('kgg')같은 오타도 지나갑니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
2절의 첫 코드를 main.ts로 저장하고 npx tsc로 검사합니다. 5번 줄의 let을 const로 바꾸면 검사 결과가 어떻게 될지 먼저 적고, 바꿔서 맞춰 봅니다. 편집기에서 mine에 마우스를 올려 let일 때와 const일 때의 타입을 견줘 봅니다.
검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.