본문으로 건너뛰기

값에서 타입 뽑기 — typeof와 as const

타입스크립트 목차타입으로 타입 만들기레슨 38 / 51

목차

타입 자리에 적는 typeof 값이름은 이미 만들어 둔 자바스크립트 값에서 검사기가 추론한 타입 모양을 그대로 뽑아내는 문법입니다. 여기에 배열이나 객체 뒤에 붙이는 as const를 함께 쓰면, 값들이 string이나 number로 넓어지지 않고 리터럴 그대로 남아 (typeof 배열)[number] 한 줄로 리터럴 유니언 타입을 뽑아낼 수 있습니다.

코드에 이미 기본 설정 객체 latte나 크기 목록 배열 ['tall', 'grande']가 있는데, 똑같은 모양의 interface나 'tall' | 'grande' 타입을 손으로 한 번 더 적으면 값이 바뀔 때마다 두 군데를 고쳐야 합니다. 이 레슨에서는 값에서 타입을 뽑는 법을 살펴봅니다.

5단계 중 1단계. 1~4번 줄: 값 latte를 만들면 검사기는 { name: string; price: number; } 모양으로 추론합니다.

1타입 자리의 typeof — 값에서 모양 뽑기

JS 다시 보기: 실행 코드(값 자리)에서 typeof latte를 부르면 'object' 같은 글자 하나를 돌려줍니다(수·글자·불리언과 typeof).

반면 type Item = typeof latte;⁠처럼 타입 자리에 적은 typeof는 실행되기 전에 검사기가 읽고 지우는 문법입니다. 검사기가 latte의 초기값에서 추론한 모양(name: string과 price: number)을 그대로 뽑아 새 타입으로 만듭니다(검사 규칙 3 · 4).

const latte = {
  name: '라테',
  price: 4500,
};
type Item = typeof latte;

const tea: Item = {
  name: '녹차',
  price: 4000,
};
console.log(
  typeof latte,
  tea.name,
);
실행 결과
object 녹차

5번 줄의 typeof latte는 타입 자리라 name: string과 price: number 두 줄 모양이 되고, 12번 줄의 typeof latte는 console.log 인자(값 자리)라 실행할 때 'object'를 찍습니다.

적은 자리예시하는 일과 결과
값 자리 (실행 코드)typeof latte실행 때 값의 종류를 글자('object')로 돌려줌
타입 자리 (: · type = 뒤)type Item = typeof latte검사 때 값의 추론 모양을 뽑고 실행 전에 지워짐

2as const와 (typeof 배열)[number]로 리터럴 유니언 뽑기

적지 않아도 붙는 타입 — 추론과 리터럴 유니언으로 값 고르기에서 봤듯이, 배열이나 객체 속성은 나중에 바뀔 수 있다고 보고 ['tall', 'grande']를 string[]로 넓혀 추론합니다(검사 규칙 3).

값 뒤에 as const를 붙이면 검사기에게 '이 안의 값들은 바뀌지 않으니 넓히지 말고 적힌 리터럴 그대로 읽기 전용(readonly)으로 추론하라'고 알려 줍니다.

const sizes = [
  'tall',
  'grande',
] as const;
type Size =
  (typeof sizes)[number];
const mySize: Size = 'short';
검사 결과 오류 1개
main.ts(7,7): error TS2322: Type '"short"' is not assignable to type '"grande" | "tall"'.

판 차이 메모: 타입스크립트 7.0은 유니언을 알파벳순으로 정렬해 "grande" | "tall"로 찍고, 플레이그라운드(6.0)는 적은 차례대로 "tall" | "grande"로 찍습니다. 두 꼴은 같은 타입입니다.

1~4번 줄에서 as const를 붙였기 때문에 typeof sizes는 readonly ["tall", "grande"]⁠가 됩니다. 앞 레슨 keyof와 속성 타입 꺼내기에서 객체 타입 뒤에 ['속성이름']을 붙여 속성 타입을 꺼냈듯이, 배열(튜플) 타입 뒤에 [number]를 붙이면 '숫자 인덱스로 꺼낼 수 있는 모든 원소의 타입'을 모아 "grande" | "tall" 리터럴 유니언을 만듭니다. 그래서 목록에 없는 'short'를 넣은 7번 줄이 TS2322로 막힙니다.

만약 4번 줄에서 as const를 빼면 7번 줄은 어떻게 될까요? 답을 정한 뒤 펼쳐 보세요.

const sizes = [
  'tall',
  'grande',
];
type Size =
  (typeof sizes)[number];
const mySize: Size = 'short';
console.log(mySize);
검사 결과와 실행 결과 보기
검사 결과 오류 없음
실행 결과
short

예상과 다르면 그곳이 배울 곳입니다. as const를 빼면 sizes는 일반 배열 string[]로 넓혀집니다. 따라서 (typeof sizes)[number]⁠도 넓은 string이 되어, 목록에 없는 'short'를 넣어도 아무 오류 없이 검사를 지나갑니다.

예제

검사 결과와 실행 결과를 먼저 적어 본 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.

결제 수단 목록 배열 ways에 as const를 붙여 값으로도 쓰고(반복문 출력), 타입 Way로도 뽑아 함수 매개변수에 썼습니다.

const ways = [
  'card',
  'cash',
] as const;
type Way =
  (typeof ways)[number];

function pay(w: Way) {
  return `결제:${w}`;
}
for (const w of ways) {
  console.log(pay(w));
}
console.log(pay('point'));

검사를 지나갈까요? 지나가지 못하면 몇 번 줄에서 막힐까요? Node로 돌리면 무엇이 찍힐까요?

검사 결과와 실행 결과 보기
검사 결과 오류 1개
main.ts(14,17): error TS2345: Argument of type '"point"' is not assignable to parameter of type '"card" | "cash"'.
실행 결과
결제:card
결제:cash
결제:point

예상과 다르면 그곳이 배울 곳입니다. Way는 "card" | "cash"이므로 11~13번 줄 반복문에서 꺼낸 w는 그대로 pay(w)에 들어가지만, 14번 줄의 'point'는 목록 밖이라 TS2345로 막힙니다. Node는 as const와 타입을 모두 지우고 실행하므로(검사 규칙 1) 세 줄이 모두 찍힙니다.

자주 틀리는 곳

틀린 생각: 타입 자리에 이미 타입 이름을 놓고 typeof Menu처럼 적는다.

typeof가 타입을 다루는 키워드라고 생각하면 인터페이스나 타입 별칭 앞에도 typeof를 붙이기 쉽습니다(타입과 값을 섞는 생각).

interface Menu {
  name: string;
}
type Copy = typeof Menu;
검사 결과 오류 1개
main.ts(4,20): error TS2693: 'Menu' only refers to a type, but is being used as a value here.

맞는 규칙: 타입 자리의 typeof 뒤에는 반드시 값 이름(변수·상수·함수 이름)이 와야 합니다(1절). 이미 타입인 Menu는 typeof 없이 그대로 씁니다.

틀린 생각: as const도 as가 들어가므로 as는 검사기에게 하는 말의 타입 단언처럼 검사를 덮는 위험한 문법이다.

6단원에서 as Order 같은 단언이 실제 값과 다른 타입을 억지로 믿게 해 위험하다고 배웠기 때문에 as const도 비슷해 보입니다(단언이 값을 바꾼다는 생각).

맞는 규칙: as const는 없는 속성이 있다고 검사기를 속이지 않습니다. 오히려 코드에 직접 적은 리터럴 값을 string으로 넓히지 말고 더 좁고 정확하게(리터럴 + readonly) 추론하라고 알려 주는 안전한 표기입니다(2절 · 검사 규칙 3).

틀린 생각: const sizes에 ['tall', 'grande']를 넣었으니 as const 없이도 원소들이 리터럴로 추론된다.

변수 선언이 const이니 배열 안의 칸들도 바뀌지 않는 상수가 될 것 같습니다(넓어지지 않는다는 생각).

맞는 규칙: const는 변수 sizes에 다른 배열을 다시 대입하지 못하게 막을 뿐, 배열 안에 .push()로 새 글자를 넣는 것까지 막지 않습니다. 배열 원소나 객체 속성까지 리터럴 그대로 고정하려면 값 뒤에 as const를 붙여야 합니다(2절).

확인 문제

1. 아래 코드에서 6번 줄 Role의 타입은 무엇일까요? 7번 줄은 검사를 지나갈까요?

const roles = [
  'admin',
  'member',
];
type Role =
  (typeof roles)[number];
const r: Role = 'guest';
힌트

1~4번 줄의 배열 뒤에 as const가 붙어 있는지 먼저 확인합니다(2절 맞혀 보기).

정답
검사 결과 오류 없음

Role의 타입은 string이고, 7번 줄은 오류 없이 검사를 지나갑니다. roles 뒤에 as const가 없어서 roles가 string[]로 넓혀졌고, (typeof roles)[number]⁠도 string이 되었기 때문입니다. 'admin' | 'member'만 허용하려면 4번 줄을 ] as const;로 고쳐야 합니다. 흔한 오답은 '7번 줄에서 TS2322로 막힌다'입니다. const roles만 보고 as const가 있는 것처럼 읽은 것입니다.

2. 기본 설정 객체 defaultConfig에서 타입을 뽑아 Config 별칭을 만들려고 합니다. 5번 줄 빈칸에 알맞은 두 단어를 채우세요.

const defaultConfig = {
  dark: false,
  fontSize: 16,
};
type Config = _____________;

const cfg: Config = {
  dark: true,
  fontSize: 20,
};
console.log(cfg.fontSize);
힌트

값 이름 defaultConfig 앞에 붙여 모양을 뽑아내는 키워드입니다(1절).

정답

빈칸은 typeof defaultConfig입니다.

const defaultConfig = {
  dark: false,
  fontSize: 16,
};
type Config =
  typeof defaultConfig;

const cfg: Config = {
  dark: true,
  fontSize: 20,
};
console.log(cfg.fontSize);
실행 결과
20

다르게 써도 되는 곳: dark: boolean과 fontSize: number 두 줄을 직접 적어도 검사는 지나지만, defaultConfig에 줄이 추가될 때 자동으로 따라 바뀌지 않습니다. 흔한 오답은 typeof 없이 defaultConfig만 적는 것입니다. 값 이름을 타입 자리에 그대로 적으면 TS2749 오류가 납니다.

3. 요일 목록 배열에서 'mon' | 'wed' | 'fri' 리터럴 유니언 타입 Day를 뽑아 'sun'을 막으려고 합니다. 빈칸 ①과 ②에 들어갈 말을 바르게 짝지은 것을 고르세요.

const days = [
  'mon',
  'wed',
  'fri',
] ①;
type Day = (typeof days)[②];
  • ① as const, ② number
  • ① as string, ② 0
  • ① readonly, ② string
힌트

배열이 넓어지지 않게 뒤에 붙이는 두 단어와, 배열 원소 전체의 타입을 꺼낼 때 대괄호 안에 적는 인덱스 타입을 떠올려 보세요(2절).

정답

첫 번째 보기(① as const, ② number)입니다. as const로 readonly ["mon", "wed", "fri"]⁠를 만든 뒤 [number]로 모든 숫자 인덱스의 원소 타입을 모으면 "fri" | "mon" | "wed" 리터럴 유니언이 됩니다.

  • 두 번째 보기에서 [0]을 적으면 첫째 원소 'mon' 하나만 꺼내집니다.
  • 세 번째 보기에서 readonly는 타입 자리에만 쓸 수 있고 값 뒤에는 as const를 씁니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 2절 첫 코드를 main.ts에 저장해 npx tsc로 TS2322가 나는 것을 확인한 뒤, 4번 줄의 as const를 지우고 다시 npx tsc를 돌려 오류가 사라지는지 비교해 보세요. 다음 레슨은 있는 타입에서 새 타입 — Pick·Omit·Partial입니다. 검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.

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

타입스크립트 목차로