ν‹°μŠ€ν† λ¦¬ λ·°

type Product = "apple" | "banana" | "orange";

const menu: Record<Product, number> = {
    apple: 1000,
    banana: 2000,
    orange: 1500
};

const products = Object.keys(menu);
// products의 νƒ€μž…μ€?

μœ„μ™€ 같은 Typescript μ½”λ“œμ—μ„œ, `products`의 νƒ€μž…μ€ 뭘둜 μΆ”λ‘ λ κΉŒ? `menu`λŠ” `Record<Product, number>` νƒ€μž…μ˜ κ°μ²΄λ‹ˆκΉŒ, 이 객체의 ν‚€μ˜ νƒ€μž…μ€ Product일 것이닀. `Object.keys()` λ©”μ†Œλ“œλŠ” 객체의 λͺ¨λ“  ν‚€λ₯Ό λ°°μ—΄λ‘œ λ°˜ν™˜ν•˜λŠ” λ©”μ†Œλ“œμ΄λ‹€. λ”°λΌμ„œ `products`의 νƒ€μž…μ€ `Product[]`둜 μΆ”λ‘ λ˜λŠ” 것이 λ§ˆλ•…ν•˜λ‹€.

 

ν•˜μ§€λ§Œ, 이 κΈ€μ˜ 제λͺ©μ—λ„ λ‚˜μ™€μžˆλ‹€μ‹œν”Ό, 정닡은 `string[]`이닀. 직접 확인해보고 μ‹ΆμœΌμ‹œλ‹€λ©΄ TS Playgroundμ—μ„œ 확인해볼 수 μžˆλ‹€.

 

μ΄μœ κ°€ 뭘까? `menu`의 ν‚€λŠ” `Product` νƒ€μž…μž„μ΄ ν™•μ‹€ν•˜μ§€ μ•Šλ‚˜? 그런데 μ™œ ꡳ이 더 넓은 λ²”μœ„μΈ `string[]`으둜 μΆ”λ‘ λ˜λŠ” κ²ƒμΌκΉŒ? μ‹€μ œλ‘œ 이 ν˜„μƒμ„ 버그라고 μƒκ°ν•œ ν•œ μ‚¬λžŒμ΄ Typescript λ ˆν¬μ§€ν† λ¦¬μ— 이슈λ₯Ό 올린 적이 μžˆλ‹€.

 

This is intentional. Types in TS are open ended. So keysof will likely be less than all properties you would get at runtime.

 

닡변은 λ‹¨ν˜Έν–ˆλ‹€. μ˜λ„μ μΈ 섀계라고 λ§ν•œλ‹€. 도무지 납득할 수 μ—†λ‹€. κ·Έ μ΄μœ λŠ” λ¬΄μ—‡μΌκΉŒ?

 

πŸ“Œ Typescript의 ꡬ쑰적 타이핑

TypeScript의 핡심 μ² ν•™ 쀑 ν•˜λ‚˜λŠ” ꡬ쑰적 타이핑이닀. μ΄λŠ” νƒ€μž… μ •μ˜κ°€ "이 μ†μ„±λ“€λ§Œ κ°€μ§€κ³  μžˆμ–΄μ•Ό ν•œλ‹€."κ°€ μ•„λ‹ˆλΌ, "적어도 이 속성듀은 κ°€μ§€κ³  μžˆμ–΄μ•Ό ν•œλ‹€"λŠ” μ˜λ―Έλ‹€.

 

이해λ₯Ό 돕기 μœ„ν•΄ μ•„λž˜ μ˜ˆμ‹œλ₯Ό 봐보자.

interface Person {
  name: string;
}

const student = {
  name: "Sean",
  grade: 3,
};

const person: Person = student;

`person` λ³€μˆ˜λŠ” `Person` νƒ€μž…μœΌλ‘œ μ„ μ–Έλ˜μ—ˆμ§€λ§Œ, μ‹€μ œ λŸ°νƒ€μž„μ—λŠ” `grade`λΌλŠ” μΆ”κ°€ 속성을 κ°€μ§„ 객체가 할당될 수 μžˆλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ νƒ€μž…μ€ 객체의 λͺ¨λ“  ν˜•νƒœλ₯Ό ν™•μ • μ§“λŠ” 것이 μ•„λ‹ˆλΌ, μ΅œμ†Œν•œμ˜ 쑰건을 검사할 뿐이닀.

 

πŸ’£  λ§Œμ•½ string[]이 μ•„λ‹ˆλΌλ©΄

Object.keys(person).forEach((key) => {
  console.log(person[key].toUpperCase());
});

Typescriptκ°€ `Object.keys(person)`의 νƒ€μž…μ„ `("name")[]`으둜 μΆ”λ‘ ν•œλ‹€κ³  κ°€μ •ν•˜μž.

  • 반볡문 λ‚΄μ˜ `key`λŠ” 무쑰건 "name"이라고 μΆ”λ‘ λœλ‹€.
  • 반볡문 λ‚΄μ˜ `person[key]`λŠ” 무쑰건 `string`으둜 μΆ”λ‘ λœλ‹€.
  • `string`μ—λŠ” `toUpperCase()` λ©”μ†Œλ“œκ°€ 있기 λ•Œλ¬Έμ—, 였λ₯˜κ°€ μ—†λ‹€ !

ν•˜μ§€λ§Œ μ‹€μ œλ‘œ λŸ°νƒ€μž„μ—μ„œμ˜ 상황을 μ‚΄νŽ΄λ³΄μž.

  • 반볡문 λ‚΄μ˜ `key`λŠ” "name"일 μˆ˜λ„, "grade"일 μˆ˜λ„ μžˆλ‹€.
  • 반볡문 λ‚΄μ˜ `person[key]`λŠ” `string`일 μˆ˜λ„, `number`일 μˆ˜λ„ μžˆλ‹€.
  • `number`μ—λŠ” `toUpperCase()` λ©”μ†Œλ“œκ°€ μ—†κΈ° λ•Œλ¬Έμ—, λŸ°νƒ€μž„ 였λ₯˜κ°€ λ°œμƒν•œλ‹€ !

결과적으둜 κ°œλ°œμžλŠ” 컴파일러λ₯Ό λ―Ώκ³  `toUpperCase()` λ©”μ†Œλ“œλ₯Ό μΌμ§€λ§Œ, λŸ°νƒ€μž„ μ—λŸ¬κ°€ λ°œμƒν•˜λŠ” 것이닀. μ΄λŸ¬ν•œ μ΄μœ μ—μ„œ TypescriptλŠ” `Object.keys()` λ©”μ†Œλ“œμ˜ λ°˜ν™˜ νƒ€μž…μ„ `string[]`으둜 μ„€μ •ν•˜λŠ” 보수적인 선택을 ν•œ 것이닀.

 

βœ… 해결법

방법 1. νƒ€μž… 단언

const products = Object.keys(menu) as Product[];

κ°€μž₯ κ°„λ‹¨ν•˜μ§€λ§Œ, κ°œλ°œμžκ°€ μ‹€μˆ˜ν•˜λ©΄ λŸ°νƒ€μž„ μ—λŸ¬λ‘œ μ΄μ–΄μ§ˆ 수 μžˆλ‹€.

 

방법 2. νƒ€μž… κ°€λ“œ + 필터링

type Product = "apple" | "banana" | "orange";
const PRODUCT_KEYS: Product[] = ["apple", "banana", "orange"];

const menu = {
  apple: 1000,
  banana: 2000,
  orange: 1500,
  grade: 3
};

function isProductKey(key: string): key is Product {
  return PRODUCT_KEYS.includes(key);
}

const safeKeys = Object.keys(menu).filter(isProductKey);

 

ν‚€ 배열을 미리 λ§Œλ“€μ–΄λ‘κ³ , `Object.keys`둜 λ‚˜μ˜¨ 킀듀이 κ·Έ λͺ©λ‘μ— μžˆλŠ”μ§€ ν™•μΈν•΄μ„œ κ±ΈλŸ¬λ‚΄λŠ” 방법이닀. μ΄ 방식을 μ“°λ©΄ μ˜ˆμƒν•˜μ§€ μ•Šμ€ ν‚€(`grade`)κ°€ 객체에 μ„žμ—¬ μžˆμ–΄λ„ filter() λ©”μ†Œλ“œμ—μ„œ κ±ΈλŸ¬μ§„λ‹€. λ”°λΌμ„œ κ²°κ³Ό λ°°μ—΄μ—λŠ” `Product`만 λ‚¨κ²Œ 되고, `safeKeys` μ—­μ‹œ νƒ€μž… κ°€λ“œμ— μ˜ν•΄ `Product[]` νƒ€μž…μœΌλ‘œ μΆ”λ‘ λœλ‹€.

 

ν•˜μ§€λ§Œ `PRODUCT_KEYS`λΌλŠ” 배열을 λ”°λ‘œ 관리해야 ν•œλ‹€λŠ” λ²ˆκ±°λ‘œμ›€μ΄ μžˆλ‹€.

 

방법 3. Zod μ‚¬μš©ν•˜κΈ°

import { z } from "zod";

const ProductSchema = z.enum(["apple", "banana", "orange"]);
type Product = z.infer<typeof ProductSchema>;

const menu = {
  apple: 1000,
  banana: 2000,
  grade: 3,
};

const keys = Object.keys(menu);

const safeKeys = keys.filter((key): key is Product => {
  const result = ProductSchema.safeParse(key);
  return result.success;
});

console.log(safeKeys);

ν‚€ λ°°μ—΄ 생성과 검증 λ‘œμ§μ„ Zodμ—κ²Œ λ§‘κΈ°λŠ” 방법이닀. ν‚€ 배열을 λ”°λ‘œ λ§Œλ“€ ν•„μš”λ„ μ—†κ³ , 필터링 λ‘œμ§λ„ 직접 κ΅¬ν˜„ν•  ν•„μš”κ°€ μ—†μ–΄μ„œ κ·Έλ‚˜λ§ˆ νŽΈν•˜κΈ΄ ν•˜λ‹€. κ·ΈλŸ¬λ‚˜ λŸ°νƒ€μž„ μ‹œ 검증 λΉ„μš©μ΄ 쑰금 λ°œμƒν•  μˆ˜λ„ 있고, μ—­μ‹œλ‚˜ μ½”λ“œ 길이가 κΈΈμ–΄μ§„λ‹€λŠ” 단점은 μ‘΄μž¬ν•œλ‹€.

 

κ²°κ΅­μ—λŠ” μ•ˆμ „μ„±κ³Ό νŽΈμ˜μ„±μ˜ νŠΈλ ˆμ΄λ“œμ˜€ν”„μΈ 것 κ°™λ‹€. 상황에 따라 κ°œλ°œμžκ°€ 적절히 μ„ νƒν•˜λŠ” 것이 λ§žμ•„ 보인닀.

 

πŸ’‘ κ²°λ‘ 

TypeScriptκ°€ `Object.keys()`λ₯Ό `string[]`으둜 μΆ”λ‘ ν•˜λŠ” 것은 버그가 μ•„λ‹ˆλΌ, ꡬ쑰적 νƒ€μ΄ν•‘μœΌλ‘œλΆ€ν„° λ°œμƒν•˜λŠ” λŸ°νƒ€μž„ μ—λŸ¬λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•œ μ•ˆμ „ μž₯μΉ˜μ΄λ‹€.

 

λΆˆνŽΈν•˜κ²Œ 느껴질 μˆ˜λ„ μžˆμ§€λ§Œ, μ΄λŠ” Typescriptκ°€ ν”„λ‘œκ·Έλž¨μ˜ μ•ˆμ „μ„±μ„ 보μž₯ν•˜κΈ° μœ„ν•œ νƒœλ„μ΄κΈ°λ„ ν•˜λ‹€. 이 원리λ₯Ό μ΄ν•΄ν•˜κ³  μœ„μ—μ„œ μ–ΈκΈ‰ν•œ 해결법을 적절히 μ μš©ν•˜λŠ” 것이 ν˜„λͺ…ν•œ μ‚¬μš©λ²•μΌ 것이닀.

'πŸ“– 곡뢀 > Javascript' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

μ‹€ν–‰ μ»¨ν…μŠ€νŠΈ  (0) 2025.11.07
곡지사항
μ΅œκ·Όμ— 올라온 κΈ€
μ΅œκ·Όμ— 달린 λŒ“κΈ€
Total
Today
Yesterday
링크
Β«   2026/04   Β»
일 μ›” ν™” 수 λͺ© 금 ν† 
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
κΈ€ 보관함