๐Ÿ”‘ keyof ์—ฐ์‚ฐ์ž: ๊ฐ์ฒด ์†์„ฑ์˜ ํƒ€์ž…์œผ๋กœ ํ™œ์šฉํ•˜๊ธฐ ๐Ÿ”‘

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ”‘ keyof ์—ฐ์‚ฐ์ž: ๊ฐ์ฒด ์†์„ฑ์˜ ํƒ€์ž…์œผ๋กœ ํ™œ์šฉํ•˜๊ธฐ ๐Ÿ”‘

 

 

์•ˆ๋…•ํ•˜์„ธ์š”, ์—ฌ๋Ÿฌ๋ถ„! ์˜ค๋Š˜์€ TypeScript์˜ ๊ฟ€ํŒ ์ค‘ ํ•˜๋‚˜์ธ 'keyof ์—ฐ์‚ฐ์ž'์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”. ์ด ๋…€์„, ๊ฐ์ฒด ์†์„ฑ์˜ ํƒ€์ž…์„ ๋‹ค๋ฃจ๋Š” ๋ฐ ์žˆ์–ด์„œ ์ง„์งœ ๋Œ€๋ฐ• ์œ ์šฉํ•˜๋‹ต๋‹ˆ๋‹ค! ๐Ÿ˜Ž

TypeScript๋ฅผ ์ฒ˜์Œ ์ ‘ํ•˜์‹œ๋Š” ๋ถ„๋“ค๋„ ์žˆ์„ ํ…Œ๋‹ˆ, ์šฐ์„  ๊ฐ„๋‹จํžˆ ์„ค๋ช…๋“œ๋ฆด๊ฒŒ์š”. TypeScript๋Š” JavaScript์˜ ์Šˆํผ์…‹ ์–ธ์–ด๋กœ, ํƒ€์ž… ์‹œ์Šคํ…œ์„ ์ถ”๊ฐ€ํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ๋†’์—ฌ์ฃผ๋Š” ๋…€์„์ด์—์š”. ๊ทธ ์ค‘์—์„œ๋„ 'keyof' ์—ฐ์‚ฐ์ž๋Š” ๊ฐ์ฒด ํƒ€์ž…์˜ ๋ชจ๋“  ํ‚ค๋ฅผ ๋ฌธ์ž์—ด ๋˜๋Š” ์ˆซ์ž ๋ฆฌํ„ฐ๋Ÿด ์œ ๋‹ˆ์˜จ์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ํŠน๋ณ„ํ•œ ๋…€์„์ด๋ž๋‹ˆ๋‹ค.

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ 'keyof'์— ๋Œ€ํ•ด ํŒŒํ—ค์ณ๋ณผ๊นŒ์š”? ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ๊ณ ๊ณ ์”ฝ~ ๐Ÿš€

1. keyof์˜ ๊ธฐ๋ณธ ๊ฐœ๋… ๐Ÿง 

'keyof'๋Š” TypeScript์—์„œ ์ œ๊ณตํ•˜๋Š” ํŠน๋ณ„ํ•œ ์—ฐ์‚ฐ์ž์˜ˆ์š”. ์ด ๋…€์„์˜ ์ฃผ์š” ์ž„๋ฌด๋Š” ๋ญ๋ƒ๊ณ ์š”? ๋ฐ”๋กœ ๊ฐ์ฒด ํƒ€์ž…์—์„œ ํ‚ค๋“ค์˜ ์œ ๋‹ˆ์˜จ ํƒ€์ž…์„ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฑฐ๋ž๋‹ˆ๋‹ค! ์–ด๋–ค ๋Š๋‚Œ์ธ์ง€ ์˜ˆ์ œ๋กœ ํ•œ๋ฒˆ ๋ณผ๊นŒ์š”?


interface Person {
  name: string;
  age: number;
  location: string;
}

type PersonKeys = keyof Person; // "name" | "age" | "location"

์œ„ ์ฝ”๋“œ์—์„œ 'PersonKeys'๋Š” "name", "age", "location"์˜ ์œ ๋‹ˆ์˜จ ํƒ€์ž…์ด ๋˜๋Š” ๊ฑฐ์ฃ . ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Person ๊ฐ์ฒด์˜ ๋ชจ๋“  ํ‚ค๋ฅผ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฑฐ์˜ˆ์š”. ์‹ ๊ธฐํ•˜์ฃ ? ๐Ÿ˜ฎ

์ด๋Ÿฐ ์‹์œผ๋กœ 'keyof'๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๊ฐ์ฒด์˜ ์†์„ฑ์— ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•จ์ˆ˜์—์„œ ๊ฐ์ฒด์˜ ํŠน์ • ํ‚ค์— ์ ‘๊ทผํ•ด์•ผ ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.


function getProperty<t k extends keyof t>(obj: T, key: K): T[K] {
  return obj[key];
}

const person: Person = {
  name: "Alice",
  age: 30,
  location: "New York"
};

const name = getProperty(person, "name"); // ํƒ€์ž…: string
const age = getProperty(person, "age"); // ํƒ€์ž…: number
const location = getProperty(person, "location"); // ํƒ€์ž…: string
</t>

์ด ์˜ˆ์ œ์—์„œ 'getProperty' ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด์™€ ํ‚ค๋ฅผ ๋ฐ›์•„์„œ ํ•ด๋‹น ํ‚ค์˜ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์š”. 'K extends keyof T'๋ผ๋Š” ์ œ์•ฝ ์กฐ๊ฑด ๋•๋ถ„์—, ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ต๋‹ˆ๋‹ค. ์•ˆ์ „ํ•˜๊ณ  ํŽธ๋ฆฌํ•˜์ฃ ? ๐Ÿ‘

๐ŸŽ“ Pro Tip: 'keyof'๋Š” ๋‹จ์ˆœํžˆ ํ‚ค๋ฅผ ๋‚˜์—ดํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์š”. ๊ฐ์ฒด์˜ ๊ตฌ์กฐ๋ฅผ ํƒ€์ž… ์‹œ์Šคํ…œ์— ๋ฐ˜์˜ํ•จ์œผ๋กœ์จ, ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ด๊ณ  ์ž๋™ ์™„์„ฑ ๊ธฐ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ํฐ ๋„์›€์„ ์ค€๋‹ต๋‹ˆ๋‹ค!

์ด๋ ‡๊ฒŒ 'keyof'์˜ ๊ธฐ๋ณธ ๊ฐœ๋…์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. ์–ด๋•Œ์š”? ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์ฃ ? ์ด์ œ ์ด ๋…€์„์„ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณผ๊นŒ์š”? ๋‹ค์Œ ์„น์…˜์—์„œ ๊ณ„์†๋ฉ๋‹ˆ๋‹ค! ๐Ÿƒโ€โ™‚๏ธ๐Ÿ’จ

2. keyof์˜ ์‹ค์ „ ํ™œ์šฉ ์‚ฌ๋ก€ ๐Ÿ› ๏ธ

์ž, ์ด์ œ 'keyof'๋ฅผ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณผ ์ฐจ๋ก€์˜ˆ์š”. ์—ฌ๋Ÿฌ๋ถ„, ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ์ถœ๋ฐœ~! ๐Ÿš—๐Ÿ’จ

2.1 ๊ฐ์ฒด์˜ ํ‚ค ํƒ€์ž… ์ œํ•œํ•˜๊ธฐ

๋จผ์ €, 'keyof'๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ์ฒด์˜ ํ‚ค ํƒ€์ž…์„ ์ œํ•œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊ฒŒ์š”. ์ด๊ฑด ํŠนํžˆ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ต๋‹ˆ๋‹ค.


interface Config {
  apiUrl: string;
  timeout: number;
  retryCount: number;
}

function updateConfig(config: Config, key: keyof Config, value: Config[keyof Config]) {
  config[key] = value;
}

const myConfig: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retryCount: 3
};

updateConfig(myConfig, "timeout", 10000); // OK
updateConfig(myConfig, "apiUrl", "https://new-api.example.com"); // OK
updateConfig(myConfig, "foo", "bar"); // ์—๋Ÿฌ: "foo"๋Š” Config์˜ ํ‚ค๊ฐ€ ์•„๋‹˜
updateConfig(myConfig, "timeout", "5000"); // ์—๋Ÿฌ: number ํƒ€์ž…์— string์„ ํ• ๋‹นํ•  ์ˆ˜ ์—†์Œ

์ด ์˜ˆ์ œ์—์„œ 'updateConfig' ํ•จ์ˆ˜๋Š” 'Config' ๊ฐ์ฒด์˜ ํ‚ค๋งŒ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ , ํ•ด๋‹น ํ‚ค์— ๋งž๋Š” ํƒ€์ž…์˜ ๊ฐ’๋งŒ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‹ค์ˆ˜๋กœ ์ž˜๋ชป๋œ ํ‚ค๋‚˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑธ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ์•ˆ์ „์„ฑ up! ๐Ÿ‘

2.2 ๋งคํ•‘๋œ ํƒ€์ž… ๋งŒ๋“ค๊ธฐ

'keyof'๋Š” ๋งคํ•‘๋œ ํƒ€์ž…์„ ๋งŒ๋“ค ๋•Œ๋„ ์•„์ฃผ ์œ ์šฉํ•ด์š”. ๋งคํ•‘๋œ ํƒ€์ž…์ด ๋ญ๋ƒ๊ณ ์š”? ๊ธฐ์กด ํƒ€์ž…์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒˆ๋กœ์šด ํƒ€์ž…์„ ๋งŒ๋“œ๋Š” ๊ฑฐ๋ž๋‹ˆ๋‹ค. ํ•œ๋ฒˆ ๋ณผ๊นŒ์š”?


interface Person {
  name: string;
  age: number;
  email: string;
}

type Nullable<t> = { [K in keyof T]: T[K] | null };

const nullablePerson: Nullable<person> = {
  name: "Alice",
  age: null,
  email: "alice@example.com"
};
</person></t>

์—ฌ๊ธฐ์„œ 'Nullable' ํƒ€์ž…์€ ์›๋ž˜ ํƒ€์ž…์˜ ๋ชจ๋“  ์†์„ฑ์„ null๊ณผ ์œ ๋‹ˆ์˜จ์œผ๋กœ ๋งŒ๋“ค์–ด์ค˜์š”. ์ฆ‰, ๋ชจ๋“  ์†์„ฑ์ด ์›๋ž˜ ํƒ€์ž…์ด๊ฑฐ๋‚˜ null์ด ๋  ์ˆ˜ ์žˆ๋Š” ๊ฑฐ์ฃ . ์ด๋Ÿฐ ์‹์œผ๋กœ 'keyof'๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ์กด ํƒ€์ž…์„ ๋ณ€ํ˜•ํ•ด์„œ ์ƒˆ๋กœ์šด ํƒ€์ž…์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”. ์™„์ „ ๊ฟ€ํŒ์ด์ฃ ? ๐Ÿฏ

2.3 ํƒ€์ž… ์•ˆ์ „ํ•œ ๊ฐ์ฒด ์ˆœํšŒ

๊ฐ์ฒด๋ฅผ ์ˆœํšŒํ•  ๋•Œ๋„ 'keyof'๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”. ํŠนํžˆ 'for...in' ๋ฃจํ”„์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.


function logPerson(person: Person) {
  for (const key in person) {
    if (person.hasOwnProperty(key)) {
      const k = key as keyof Person;
      console.log(`${k}: ${person[k]}`);
    }
  }
}

const alice: Person = {
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

logPerson(alice);

์ด ์˜ˆ์ œ์—์„œ 'key as keyof Person'์€ TypeScript์—๊ฒŒ 'key'๊ฐ€ 'Person' ํƒ€์ž…์˜ ํ‚ค๋ผ๋Š” ๊ฑธ ์•Œ๋ ค์ฃผ๋Š” ๊ฑฐ์˜ˆ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด 'person[k]'์—์„œ ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ณ , ๊ฐ ์†์„ฑ์— ์•ˆ์ „ํ•˜๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ๐Ÿ‘€

๐Ÿ’ก Tip: 'keyof'๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด์˜ ๊ตฌ์กฐ๋ฅผ ํƒ€์ž… ์‹œ์Šคํ…œ์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋Š” ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ด๊ณ , ๋ฆฌํŒฉํ† ๋ง์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ต๋‹ˆ๋‹ค. ํŠนํžˆ ํฐ ํ”„๋กœ์ ํŠธ์—์„œ ์ด๋Ÿฐ ์žฅ์ ์ด ๋น›์„ ๋ฐœํ•œ๋‹ค๋Š” ๊ฑธ ๊ธฐ์–ตํ•˜์„ธ์š”!

์–ด๋•Œ์š”? 'keyof'์˜ ์‹ค์ „ ํ™œ์šฉ ์‚ฌ๋ก€๋ฅผ ๋ณด๋‹ˆ ์ดํ•ด๊ฐ€ ์ข€ ๋” ์ž˜ ๋˜์‹œ๋‚˜์š”? ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„๋„ 'keyof'๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋” ์•ˆ์ „ํ•˜๊ณ  ์œ ์—ฐํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” 'keyof'์™€ ๊ด€๋ จ๋œ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค์„ ์‚ดํŽด๋ณผ ๊ฑฐ๋‹ˆ๊นŒ ๊ธฐ๋Œ€ํ•ด์ฃผ์„ธ์š”! ๐Ÿ˜‰

3. keyof์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค ๐Ÿš€

์ž, ์ด์ œ 'keyof'์— ๋Œ€ํ•ด ๊ธฐ๋ณธ์ ์ธ ์ดํ•ด๋Š” ํ•˜์…จ์„ ๊ฑฐ์˜ˆ์š”. ๊ทธ๋Ÿผ ์ด์ œ ์ข€ ๋” ์‹ฌํ™”๋œ ๋‚ด์šฉ์œผ๋กœ ๋“ค์–ด๊ฐ€๋ณผ๊นŒ์š”? 'keyof'์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ๊ฐ•๋ ฅํ•ด์ง€๋Š” TypeScript์˜ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค์„ ์†Œ๊ฐœํ•ด๋“œ๋ฆด๊ฒŒ์š”. ์ค€๋น„๋˜์…จ๋‚˜์š”? Let's go! ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ

3.1 ์กฐ๊ฑด๋ถ€ ํƒ€์ž…๊ณผ keyof

์กฐ๊ฑด๋ถ€ ํƒ€์ž…์€ TypeScript์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ธ๋ฐ์š”, 'keyof'์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ์œ ์—ฐํ•œ ํƒ€์ž… ์ •์˜๊ฐ€ ๊ฐ€๋Šฅํ•ด์ ธ์š”.


type IfHasEmail<t> = "email" extends keyof T ? T["email"] : never;

interface Person {
  name: string;
  age: number;
  email: string;
}

interface Company {
  name: string;
  address: string;
}

type PersonEmail = IfHasEmail<person>; // string
type CompanyEmail = IfHasEmail<company>; // never
</company></person></t>

์ด ์˜ˆ์ œ์—์„œ 'IfHasEmail' ํƒ€์ž…์€ ์ฃผ์–ด์ง„ ํƒ€์ž… T์— 'email' ์†์„ฑ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์žˆ๋‹ค๋ฉด ๊ทธ ํƒ€์ž…์„, ์—†๋‹ค๋ฉด 'never' ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŠน์ • ์†์„ฑ์˜ ์กด์žฌ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ์™„์ „ ์ฉ”์ฃ ? ๐Ÿ˜Ž

3.2 ์ œ๋„ค๋ฆญ ์ œ์•ฝ ์กฐ๊ฑด์œผ๋กœ keyof ํ™œ์šฉํ•˜๊ธฐ

์ œ๋„ค๋ฆญ๊ณผ 'keyof'๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ด๋ฉด์„œ๋„ ์œ ์—ฐํ•œ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”.


function pluck<t k extends keyof t>(obj: T, keys: K[]): T[K][] {
  return keys.map(key => obj[key]);
}

const person = {
  name: "Alice",
  age: 30,
  email: "alice@example.com",
  favoriteColor: "blue"
};

const nameAndAge = pluck(person, ["name", "age"]); // ["Alice", 30]
const colorAndEmail = pluck(person, ["favoriteColor", "email"]); // ["blue", "alice@example.com"]
// const invalid = pluck(person, ["name", "invalid"]); // ์ปดํŒŒ์ผ ์—๋Ÿฌ!
</t>

์ด 'pluck' ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด์—์„œ ์ง€์ •๋œ ํ‚ค๋“ค์˜ ๊ฐ’๋งŒ ์ถ”์ถœํ•ด์š”. 'K extends keyof T'๋ผ๋Š” ์ œ์•ฝ ์กฐ๊ฑด ๋•๋ถ„์—, ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ต๋‹ˆ๋‹ค. ์•ˆ์ „ํ•˜๊ณ  ํŽธ๋ฆฌํ•˜์ฃ ? ๐Ÿ‘

3.3 Record ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…๊ณผ keyof

'Record' ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…์€ 'keyof'์™€ ๊ถํ•ฉ์ด ์ข‹์€ ๋…€์„์ด์—์š”. ๊ฐ์ฒด์˜ ํ‚ค-๊ฐ’ ์Œ์„ ํƒ€์ž…์œผ๋กœ ์ •์˜ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.


type Fruit = "apple" | "banana" | "orange";
type FruitInfo = { color: string; taste: string };

type FruitCatalog = Record<fruit fruitinfo>;

const fruitCatalog: FruitCatalog = {
  apple: { color: "red", taste: "sweet" },
  banana: { color: "yellow", taste: "sweet" },
  orange: { color: "orange", taste: "citrusy" }
};

type FruitColors = Record<keyof fruitcatalog string>;

const fruitColors: FruitColors = {
  apple: "red",
  banana: "yellow",
  orange: "orange"
};
</keyof></fruit>

์—ฌ๊ธฐ์„œ 'FruitCatalog'๋Š” ๊ฐ ๊ณผ์ผ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๊ณ , 'FruitColors'๋Š” 'FruitCatalog'์˜ ํ‚ค๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์™€์„œ ์ƒˆ๋กœ์šด ํƒ€์ž…์„ ๋งŒ๋“ค์–ด๋ƒˆ์–ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ์กด ํƒ€์ž…์˜ ๊ตฌ์กฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ์™„์ „ ๊ฟ€ํŒ์ด์ฃ ? ๐Ÿฏ

๐Ÿ” Deep Dive: 'keyof'์™€ 'Record'๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด, ๊ฐ์ฒด์˜ ๊ตฌ์กฐ๋ฅผ ๋™์ ์œผ๋กœ ์ •์˜ํ•˜๋ฉด์„œ๋„ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋Š” ํŠนํžˆ API ์‘๋‹ต ์ฒ˜๋ฆฌ๋‚˜ ์„ค์ • ๊ฐ์ฒด ๊ด€๋ฆฌ ๋“ฑ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค!

์–ด๋– ์„ธ์š”? 'keyof'์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค์„ ๋ณด๋‹ˆ TypeScript์˜ ์ง„๊ฐ€๊ฐ€ ๋” ์ž˜ ๋ณด์ด์‹œ๋‚˜์š”? ์ด๋Ÿฐ ๊ธฐ๋ฒ•๋“ค์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋”์šฑ ์•ˆ์ „ํ•˜๊ณ  ์œ ์—ฐํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” 'keyof'๋ฅผ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”. ๊ธฐ๋Œ€๋˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜‰

4. keyof๋ฅผ ํ™œ์šฉํ•œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์˜ˆ์ œ ๐Ÿ’ผ

์ž, ์ด์ œ 'keyof'๋ฅผ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”. ์‹ค์ „์—์„œ ์–ด๋–ป๊ฒŒ ์“ฐ์ด๋Š”์ง€ ๋ณด๋ฉด ๋” ์ž˜ ์ดํ•ด๋  ๊ฑฐ์˜ˆ์š”. ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ๊ณ ๊ณ ์”ฝ~! ๐Ÿš€

4.1 API ์‘๋‹ต ์ฒ˜๋ฆฌํ•˜๊ธฐ

์›น ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค ๋ณด๋ฉด API ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ผ์ด ๋งŽ์ฃ ? ์ด๋•Œ 'keyof'๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ด๋ฉด์„œ๋„ ์œ ์—ฐํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”.


interface ApiResponse {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

function processApiResponse<k extends keyof apiresponse>(
  response: ApiResponse,
  key: K,
  processor: (value: ApiResponse[K]) => void
) {
  processor(response[key]);
}

const response: ApiResponse = {
  userId: 1,
  id: 1,
  title: "delectus aut autem",
  completed: false
};

processApiResponse(response, "title", (title) => {
  console.log(title.toUpperCase());
});

processApiResponse(response, "completed", (completed) => {
  console.log(completed ? "Task done!" : "Task pending...");
});

// ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ!
// processApiResponse(response, "invalid", (value) => {
//   console.log(value);
// });
</k>

์ด ์˜ˆ์ œ์—์„œ 'processApiResponse' ํ•จ์ˆ˜๋Š” API ์‘๋‹ต์˜ ํŠน์ • ํ•„๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜์˜ˆ์š”. 'K extends keyof ApiResponse'๋ผ๋Š” ์ œ์•ฝ ์กฐ๊ฑด ๋•๋ถ„์—, ApiResponse์˜ ํ‚ค๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๊ฐ ํ‚ค์— ๋งž๋Š” ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์˜ ๊ฐ’๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ์•ˆ์ „ํ•˜๊ณ  ํŽธ๋ฆฌํ•˜์ฃ ? ๐Ÿ‘

4.2 ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

์›น ํผ์„ ๋‹ค๋ฃฐ ๋•Œ 'keyof'๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํƒ€์ž… ์•ˆ์ „ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.


interface FormValues {
  username: string;
  email: string;
  age: number;
}

type FormErrors = Partial<record formvalues string>>;

function validate(values: FormValues): FormErrors {
  const errors: FormErrors = {};

  if (values.username.length < 3) {
    errors.username = "Username must be at least 3 characters long";
  }

  if (!values.email.includes("@")) {
    errors.email = "Invalid email address";
  }

  if (values.age < 18) {
    errors.age = "You must be at least 18 years old";
  }

  return errors;
}

const formValues: FormValues = {
  username: "Al",
  email: "alice@example",
  age: 16
};

const errors = validate(formValues);
console.log(errors);
// {
//   username: "Username must be at least 3 characters long",
//   email: "Invalid email address",
//   age: "You must be at least 18 years old"
// }
</record>

์ด ์˜ˆ์ œ์—์„œ 'FormErrors' ํƒ€์ž…์€ 'FormValues'์˜ ๋ชจ๋“  ํ‚ค๋ฅผ ์˜ต์…”๋„ํ•œ ๋ฌธ์ž์—ด ๊ฐ’์œผ๋กœ ๋งŒ๋“ค์–ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ํผ ์ฒ˜๋ฆฌ๊ฐ€ ํ•œ๊ฒฐ ํŽธํ•ด์ง€๊ฒ ์ฃ ? ๐Ÿ˜Œ

4.3 ์„ค์ • ๊ฐ์ฒด ๊ด€๋ฆฌ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ค์ •์„ ๊ด€๋ฆฌํ•  ๋•Œ๋„ 'keyof'๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ข‹์•„์š”. ํŠนํžˆ ์„ค์ •๊ฐ’์„ ๋™์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ต๋‹ˆ๋‹ค.


interface AppConfig {
  apiUrl: string;
  theme: "light" | "dark";
  language: "en" | "ko" | "ja";
  notifications: boolean;
}

class ConfigManager {
  private config: AppConfig;

  constructor(initialConfig: AppConfig) {
    this.config = initialConfig;
  }

  get<k extends keyof appconfig>(key: K): AppConfig[K] {
    return this.config[key];
  }

  set<k extends keyof appconfig>(key: K, value: AppConfig[K]): void {
    this.config[key] = value;
  }

  getAll(): AppConfig {
    return this.config;
  }
}

const configManager = new ConfigManager({
  apiUrl: "https://api.example.com",
  theme: "light",
  language: "en",
  notifications: true
});

console.log(configManager.get("theme")); // "light"
configManager.set("theme", "dark");
console.log(configManager.get("theme")); // "dark"

// ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ!
// configManager.set("theme", "blue");
// configManager.set("invalidKey", "value");
</k></k>

์ด 'ConfigManager' ํด๋ž˜์Šค๋Š” 'keyof'๋ฅผ ํ™œ์šฉํ•ด ํƒ€์ž… ์•ˆ์ „ํ•œ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ–ˆ์–ด์š”. 'get' ๋ฉ”์„œ๋“œ์™€ 'set' ๋ฉ”์„œ๋“œ๋Š” 'AppConfig'์˜ ํ‚ค๋งŒ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ , ๊ฐ ํ‚ค์— ๋งž๋Š” ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์˜ ๊ฐ’๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ๋”์šฑ ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๐Ÿ‘จโ€๐Ÿ’ป

๐Ÿ’ก Pro Tip: 'keyof'๋ฅผ ํ™œ์šฉํ•œ ์ด๋Ÿฐ ํŒจํ„ด๋“ค์€ ํŠนํžˆ ํฐ ๊ทœ๋ชจ์˜ ํ”„๋กœ์ ํŠธ์—์„œ ๋น›์„ ๋ฐœํ•ด์š”. ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ด๊ณ , ์‹ค์ˆ˜๋ฅผ ์ค„์ด๋ฉฐ, ๋ฆฌํŒฉํ† ๋ง์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ต๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ๋ณต์žกํ•œ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•  ๋•Œ ์ด๋Ÿฐ ๊ธฐ๋ฒ•๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ์ •๋ง ํฐ ๋„์›€์ด ๋  ๊ฑฐ์˜ˆ์š”!

์–ด๋– ์„ธ์š”? ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ 'keyof'๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฐ์ด ์ข€ ์˜ค์‹œ๋‚˜์š”? ์ด๋Ÿฐ ๊ธฐ๋ฒ•๋“ค์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋”์šฑ ์•ˆ์ „ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” 'keyof'๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ๋“ค์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”. ๊ธฐ๋Œ€๋˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜‰

5. keyof ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ โš ๏ธ

์ž, ์ด์ œ 'keyof'์˜ ๊ฐ•๋ ฅํ•จ์— ๋Œ€ํ•ด ์ถฉ๋ถ„ํžˆ ์•Œ์•„๋ณด์…จ์ฃ ? ํ•˜์ง€๋งŒ ๋ชจ๋“  ๋„๊ตฌ๊ฐ€ ๊ทธ๋ ‡๋“ฏ, 'keyof'๋„ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ด์š”. ์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” 'keyof'๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ๋“ค์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”. ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ๊ณ ๊ณ ! ๐Ÿš€

5.1 ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜์™€ keyof

์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์‚ฌ์šฉํ•œ ํƒ€์ž…์— 'keyof'๋ฅผ ์ ์šฉํ•  ๋•Œ๋Š” ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ด์š”. ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”.


interface StringMap {
  [key: string]: string;
}

type StringMapKeys = keyof StringMap; // string | number

์–ด๋ผ? 'StringMapKeys'๊ฐ€ 'string'๋งŒ ๋  ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜์…จ๋‚˜์š”? ์‹ค์ œ๋กœ๋Š” 'string | number'๊ฐ€ ๋ผ์š”. ์™œ ๊ทธ๋Ÿด๊นŒ์š”? JavaScript์—์„œ ๊ฐ์ฒด์˜ ํ‚ค๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜๋˜๊ธฐ ๋•Œ๋ฌธ์ด์—์š”. ๊ทธ๋ž˜์„œ ์ˆซ์ž ํ‚ค๋„ ์œ ํšจํ•œ ํ‚ค๋กœ ์ทจ๊ธ‰๋˜๋Š” ๊ฑฐ์ฃ .

์ด๋Ÿฐ ํŠน์„ฑ ๋•Œ๋ฌธ์— ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”. ํŠนํžˆ strictํ•œ ํƒ€์ž… ์ฒดํฌ๋ฅผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

5.2 ์œ ๋‹ˆ์˜จ ํƒ€์ž…๊ณผ keyof

์œ ๋‹ˆ์˜จ ํƒ€์ž…์— 'keyof'๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋„ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ด์š”. ๊ฒฐ๊ณผ๊ฐ€ ์ง๊ด€์ ์ด์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”.


interface Dog {
  name: string;
  breed: string;
}

interface Cat {
  name: string;
  lives: number;
}

type Pet = Dog | Cat;

type PetKeys = keyof Pet; // "name"

'PetKeys'๊ฐ€ "name" | "breed" | "lives"๊ฐ€ ๋  ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜์…จ๋‚˜์š”? ์‹ค์ œ๋กœ๋Š” "name"๋งŒ ๋ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด 'keyof'๋Š” ์œ ๋‹ˆ์˜จ ํƒ€์ž…์˜ ๋ชจ๋“  ๋ฉค๋ฒ„์— ๊ณตํ†ต์œผ๋กœ ์กด์žฌํ•˜๋Š” ํ‚ค๋งŒ ์ถ”์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์—์š”. ์ด๋Ÿฐ ํŠน์„ฑ์„ ์ž˜ ์ดํ•ด ํ•˜๊ณ  ์žˆ์–ด์•ผ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

5.3 ํƒ€์ž… ์ถ”๋ก ๊ณผ keyof

๋•Œ๋กœ๋Š” TypeScript์˜ ํƒ€์ž… ์ถ”๋ก ์ด 'keyof'์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋  ๋•Œ ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์–ด์š”. ํŠนํžˆ ์ œ๋„ค๋ฆญ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


function getProperty<t k extends keyof t>(obj: T, key: K) {
  return obj[key];
}

const person = { name: "Alice", age: 30 };
const name = getProperty(person, "name"); // OK
const age = getProperty(person, "age"); // OK

// ๋‹ค์Œ์€ ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค!
// const invalid = getProperty(person, "invalid");
</t>

์ด ์˜ˆ์ œ์—์„œ 'person' ๊ฐ์ฒด์˜ ํƒ€์ž…์€ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธ๋˜์ง€ ์•Š์•˜์ง€๋งŒ, TypeScript๋Š” ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด๋กœ๋ถ€ํ„ฐ ํƒ€์ž…์„ ์ถ”๋ก ํ•ด์š”. ๊ทธ๋ž˜์„œ 'getProperty' ํ•จ์ˆ˜๋Š” 'person' ๊ฐ์ฒด์˜ ์‹ค์ œ ํ‚ค๋งŒ ํ—ˆ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์›ํ•˜๋Š” ๋™์ž‘์ด์ง€๋งŒ, ๋•Œ๋กœ๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ œ์•ฝ์„ ๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด์š”.

5.4 readonly ์†์„ฑ๊ณผ keyof

'readonly' ์†์„ฑ์„ ๊ฐ€์ง„ ํƒ€์ž…์— 'keyof'๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋„ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ด์š”. 'readonly' ์†์„ฑ์€ 'keyof'์˜ ๊ฒฐ๊ณผ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด์ฃ .


interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

type PersonKeys = keyof ReadonlyPerson; // "name" | "age"

const person: ReadonlyPerson = { name: "Alice", age: 30 };
let key: PersonKeys = "name";
console.log(person[key]); // OK
person[key] = "Bob"; // ์ปดํŒŒ์ผ ์—๋Ÿฌ: ์ฝ๊ธฐ ์ „์šฉ ์†์„ฑ์ด๋ฏ€๋กœ 'name'์— ํ• ๋‹นํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

'keyof'๋Š” 'readonly' ์†์„ฑ์„ ๋ฌด์‹œํ•˜๊ณ  ๋ชจ๋“  ํ‚ค๋ฅผ ์ถ”์ถœํ•ด์š”. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ ๊ทธ ์†์„ฑ์— ๊ฐ’์„ ํ• ๋‹นํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ํŠน์„ฑ์„ ์ž˜ ์ดํ•ดํ•˜๊ณ  ์žˆ์–ด์•ผ ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ์ •ํ™•ํžˆ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.

โš ๏ธ Warning: 'keyof'๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํ•ญ์ƒ ๊ฒฐ๊ณผ ํƒ€์ž…์„ ์ž˜ ํ™•์ธํ•ด์•ผ ํ•ด์š”. ํŠนํžˆ ๋ณต์žกํ•œ ํƒ€์ž…์ด๋‚˜ ์ œ๋„ค๋ฆญ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋”์šฑ ๊ทธ๋ ‡์ฃ . TypeScript์˜ ํƒ€์ž… ์ถ”๋ก ์„ ๊ณผ์‹ ํ•˜์ง€ ๋ง๊ณ , ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์—์š”.

์–ด๋– ์„ธ์š”? 'keyof'๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ๋“ค์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. ์ด๋Ÿฐ ์ฃผ์˜์‚ฌํ•ญ๋“ค์„ ์ž˜ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์œผ๋ฉด, 'keyof'๋ฅผ ๋”์šฑ ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”. ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ด๋ฉด์„œ๋„ ์œ ์—ฐํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋  ๊ฑฐ๋ž๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” 'keyof'์™€ ๊ด€๋ จ๋œ ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ๋“ค์„ ์‚ดํŽด๋ณผ ๊ฑฐ์˜ˆ์š”. ๊ธฐ๋Œ€๋˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜‰

6. keyof ๊ด€๋ จ FAQ ๐Ÿค”

์ž, ์ด์ œ 'keyof'์— ๋Œ€ํ•ด ๊ฝค ๋งŽ์ด ์•Œ์•„๋ณด์…จ์ฃ ? ํ•˜์ง€๋งŒ ์•„์ง ๊ถ๊ธˆํ•œ ์ ๋“ค์ด ์žˆ์„ ๊ฑฐ์˜ˆ์š”. ์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” 'keyof'์™€ ๊ด€๋ จํ•ด ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ๋“ค์„ ๋ชจ์•„๋ดค์–ด์š”. ํ•จ๊ป˜ ์‚ดํŽด๋ณผ๊นŒ์š”? Let's go! ๐Ÿš€

Q1: keyof์™€ typeof์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

A: 'keyof'์™€ 'typeof'๋Š” ๋น„์Šทํ•ด ๋ณด์ด์ง€๋งŒ ์™„์ „ํžˆ ๋‹ค๋ฅธ ์—ฐ์‚ฐ์ž์˜ˆ์š”.

  • 'keyof'๋Š” ํƒ€์ž… ๋˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ๋“  ํ‚ค๋ฅผ ์œ ๋‹ˆ์˜จ ํƒ€์ž…์œผ๋กœ ์ถ”์ถœํ•ด์š”.
  • 'typeof'๋Š” ๊ฐ’์˜ ํƒ€์ž…์„ ์ถ”๋ก ํ•ด์š”.

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊นŒ์š”?


const person = { name: "Alice", age: 30 };

type PersonKeys = keyof typeof person; // "name" | "age"
type PersonType = typeof person; // { name: string; age: number; }

์—ฌ๊ธฐ์„œ 'keyof typeof'๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ’์œผ๋กœ๋ถ€ํ„ฐ ํ‚ค ํƒ€์ž…์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์–ด์š”. ๊ฝค ์œ ์šฉํ•œ ์กฐํ•ฉ์ด์ฃ ? ๐Ÿ˜‰

Q2: keyof๋กœ ์ถ”์ถœํ•œ ํ‚ค๋ฅผ ๋‹ค์‹œ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‚˜์š”?

A: ๋„ค, ๊ฐ€๋Šฅํ•ด์š”! ํ•˜์ง€๋งŒ ์•ฝ๊ฐ„์˜ ํŠธ๋ฆญ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


interface Person {
  name: string;
  age: number;
}

const personKey: keyof Person = "name";
const key: "name" | "age" = personKey;

function getPersonValue(key: keyof Person) {
  const person: Person = { name: "Alice", age: 30 };
  return person[key];
}

const value = getPersonValue("name"); // OK

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด 'keyof'๋กœ ์ถ”์ถœํ•œ ํ‚ค๋ฅผ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”. ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋ณด์žฅํ•˜์ง€๋Š” ์•Š์œผ๋‹ˆ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ด์š”.

Q3: keyof๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ์—†๋‚˜์š”?

A: ๊ฑฑ์ • ๋งˆ์„ธ์š”! 'keyof'๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์—๋งŒ ์ž‘๋™ํ•˜๋Š” TypeScript์˜ ํƒ€์ž… ์—ฐ์‚ฐ์ž์˜ˆ์š”. ๋”ฐ๋ผ์„œ ๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ์—๋Š” ์ „ํ˜€ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์•„์š”. ์˜คํžˆ๋ ค ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์—ฌ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋ฅผ ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

Q4: keyof๋ฅผ ๋ฐฐ์—ด์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‚˜์š”?

A: ๋„ค, ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”! ํ•˜์ง€๋งŒ ๊ฒฐ๊ณผ๊ฐ€ ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฃผ์˜ํ•ด์•ผ ํ•ด์š”.


type ArrayKeys = keyof string[]; // number | "length" | "push" | "pop" | "concat" | ...

๋ฐฐ์—ด์— 'keyof'๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ธ๋ฑ์Šค(number)์™€ ๋ฐฐ์—ด์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ์œ ๋‹ˆ์˜จ ํƒ€์ž…์œผ๋กœ ์ถ”์ถœ๋ผ์š”. ์ด๋Š” ๋ฐฐ์—ด์ด ์‚ฌ์‹ค ๊ฐ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์ด์—์š”.

Q5: keyof์™€ mapped types๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‚˜์š”?

A: ๋„ค, 'keyof'์™€ mapped types๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์•„์ฃผ ๊ฐ•๋ ฅํ•œ ํƒ€์ž… ๋ณ€ํ™˜์„ ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊นŒ์š”?


type Optional<t> = {
  [K in keyof T]?: T[K];
};

interface Person {
  name: string;
  age: number;
}

type OptionalPerson = Optional<person>;
// ๊ฒฐ๊ณผ: { name?: string; age?: number; }
</person></t>

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ชจ๋“  ์†์„ฑ์„ ์„ ํƒ์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ํƒ€์ž…์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”. ์ด๋Ÿฐ ํŒจํ„ด์€ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์ •๋ง ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋œ๋‹ต๋‹ˆ๋‹ค!

๐Ÿ’ก Pro Tip: 'keyof'์™€ mapped types๋ฅผ ์กฐํ•ฉํ•˜๋ฉด ๊ธฐ์กด ํƒ€์ž…์„ ๋ณ€ํ˜•ํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ด๊ณ  ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. ํŠนํžˆ ํฐ ํ”„๋กœ์ ํŠธ์—์„œ ์ด๋Ÿฐ ๊ธฐ๋ฒ•๋“ค์ด ๋น›์„ ๋ฐœํ•œ๋‹ค๋Š” ๊ฑธ ๊ธฐ์–ตํ•˜์„ธ์š”!

์–ด๋– ์„ธ์š”? 'keyof'์— ๋Œ€ํ•ด ๊ถ๊ธˆํ–ˆ๋˜ ์ ๋“ค์ด ์ข€ ํ•ด์†Œ๋˜์…จ๋‚˜์š”? ์ด๋Ÿฐ ์งˆ๋ฌธ๋“ค์„ ํ†ตํ•ด 'keyof'์˜ ๋‹ค์–‘ํ•œ ์ธก๋ฉด์„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์—ˆ๊ธธ ๋ฐ”๋ผ์š”. 'keyof'๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ง€๋งŒ, ๊ทธ๋งŒํผ ์„ธ์‹ฌํ•˜๊ฒŒ ๋‹ค๋ค„์•ผ ํ•˜๋Š” ๋…€์„์ด์—์š”. ์ž˜ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์˜ TypeScript ์ฝ”๋“œ๋ฅผ ํ•œ์ธต ๋” ๊ฒฌ๊ณ ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค„ ๊ฑฐ์˜ˆ์š”. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” 'keyof'๋ฅผ ํ™œ์šฉํ•œ ์‹ค์ „ ์˜ˆ์ œ๋ฅผ ๋” ์ž์„ธํžˆ ์‚ดํŽด๋ณผ ๊ฑฐ์˜ˆ์š”. ๊ธฐ๋Œ€๋˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜‰

7. keyof๋ฅผ ํ™œ์šฉํ•œ ์‹ค์ „ ์˜ˆ์ œ ๐Ÿ’ป

์ž, ์ด์ œ 'keyof'์— ๋Œ€ํ•ด ๊ฝค ๋งŽ์ด ์•Œ๊ฒŒ ๋˜์…จ์ฃ ? ์ด๋ก ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๋ณด๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด์š”. ์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” 'keyof'๋ฅผ ํ™œ์šฉํ•œ ์‹ค์ „ ์˜ˆ์ œ๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณผ ๊ฑฐ์˜ˆ์š”. ์ค€๋น„๋˜์…จ๋‚˜์š”? ๊ทธ๋Ÿผ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”? ๐Ÿš€

์˜ˆ์ œ 1: ํƒ€์ž… ์•ˆ์ „ํ•œ ๊ฐ์ฒด ๋ณต์‚ฌ ํ•จ์ˆ˜

๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌํ•  ๋•Œ ํŠน์ • ์†์„ฑ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋ณต์‚ฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”? 'keyof'๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ์ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.


function partialCopy<t k extends keyof t>(obj: T, keys: K[]): Pick<t k> {
  const result = {} as Pick<t k>;
  keys.forEach(key => {
    result[key] = obj[key];
  });
  return result;
}

interface Person {
  name: string;
  age: number;
  address: string;
  phone: string;
}

const person: Person = {
  name: "Alice",
  age: 30,
  address: "123 Main St",
  phone: "555-1234"
};

const partialPerson = partialCopy(person, ["name", "age"]);
console.log(partialPerson); // { name: "Alice", age: 30 }

// ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ!
// const invalidCopy = partialCopy(person, ["name", "invalid"]);
</t></t></t>

์ด ์˜ˆ์ œ์—์„œ 'partialCopy' ํ•จ์ˆ˜๋Š” ์›๋ณธ ๊ฐ์ฒด์™€ ๋ณต์‚ฌํ•  ํ‚ค ๋ฐฐ์—ด์„ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์š”. 'K extends keyof T'๋ผ๋Š” ์ œ์•ฝ ์กฐ๊ฑด ๋•๋ถ„์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์š”. ์•ˆ์ „ํ•˜๊ณ  ์œ ์—ฐํ•˜์ฃ ? ๐Ÿ‘

์˜ˆ์ œ 2: ํƒ€์ž… ์•ˆ์ „ํ•œ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ๋•Œ 'keyof'๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ํฌ๊ฒŒ ๋†’์ผ ์ˆ˜ ์žˆ์–ด์š”. ํ•œ๋ฒˆ ๋ณผ๊นŒ์š”?


type EventMap = {
  click: { x: number; y: number };
  change: { oldValue: string; newValue: string };
  submit: { data: object };
}

class EventEmitter {
  private listeners: Partial<{ [K in keyof EventMap]: ((data: EventMap[K]) => void)[] }> = {};

  on<k extends keyof eventmap>(event: K, callback: (data: EventMap[K]) => void) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(callback);
  }

  emit<k extends keyof eventmap>(event: K, data: EventMap[K]) {
    if (this.listeners[event]) {
      this.listeners[event]!.forEach(callback => callback(data));
    }
  }
}

const emitter = new EventEmitter();

emitter.on("click", ({ x, y }) => {
  console.log(`Clicked at (${x}, ${y})`);
});

emitter.on("change", ({ oldValue, newValue }) => {
  console.log(`Value changed from ${oldValue} to ${newValue}`);
});

emitter.emit("click", { x: 10, y: 20 });
emitter.emit("change", { oldValue: "old", newValue: "new" });

// ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ!
// emitter.on("invalid", () => {});
// emitter.emit("click", { invalid: "data" });
</k></k>

์ด ์˜ˆ์ œ์—์„œ 'EventEmitter' ํด๋ž˜์Šค๋Š” 'keyof'๋ฅผ ์‚ฌ์šฉํ•ด ํƒ€์ž… ์•ˆ์ „ํ•œ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ–ˆ์–ด์š”. ๊ฐ ์ด๋ฒคํŠธ ํƒ€์ž…์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ณ , ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์š”. ๋ฉ‹์ง€์ฃ ? ๐Ÿ˜Ž

์˜ˆ์ œ 3: ํƒ€์ž… ์•ˆ์ „ํ•œ ์ƒํƒœ ๊ด€๋ฆฌ

์ƒํƒœ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋„ 'keyof'๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ข‹์•„์š”. Redux ์Šคํƒ€์ผ์˜ ๊ฐ„๋‹จํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”?


interface State {
  user: { name: string; age: number } | null;
  posts: string[];
  isLoading: boolean;
}

type Action =
  | { type: "SET_USER"; payload: State["user"] }
  | { type: "ADD_POST"; payload: string }
  | { type: "SET_LOADING"; payload: boolean };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "SET_USER":
      return { ...state, user: action.payload };
    case "ADD_POST":
      return { ...state, posts: [...state.posts, action.payload] };
    case "SET_LOADING":
      return { ...state, isLoading: action.payload };
    default:
      return state;
  }
}

function createStore<s>(initialState: S, reducer: (state: S, action: any) => S) {
  let state = initialState;

  return {
    getState: () => state,
    dispatch: (action: any) => {
      state = reducer(state, action);
    },
    select: <k extends keyof s>(selector: K): S[K] => state[selector]
  };
}

const store = createStore<state>(
  { user: null, posts: [], isLoading: false },
  reducer
);

store.dispatch({ type: "SET_USER", payload: { name: "Alice", age: 30 } });
store.dispatch({ type: "ADD_POST", payload: "Hello, world!" });

console.log(store.select("user")); // { name: "Alice", age: 30 }
console.log(store.select("posts")); // ["Hello, world!"]

// ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ!
// console.log(store.select("invalid"));
</state></k></s>

์ด ์˜ˆ์ œ์—์„œ 'createStore' ํ•จ์ˆ˜๋Š” 'keyof'๋ฅผ ์‚ฌ์šฉํ•ด ํƒ€์ž… ์•ˆ์ „ํ•œ 'select' ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ์–ด์š”. ์ด ๋ฉ”์„œ๋“œ๋Š” ์ƒํƒœ์˜ ํŠน์ • ๋ถ€๋ถ„๋งŒ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ƒํƒœ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ฃ . ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ํ•œ๊ฒฐ ์•ˆ์ „ํ•ด์กŒ์–ด์š”! ๐Ÿ‘

๐ŸŒŸ Advanced Tip: ์ด๋Ÿฐ ํŒจํ„ด๋“ค์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•  ๋•Œ๋Š” ์ƒํ™ฉ์— ๋งž๊ฒŒ ์กฐ๊ธˆ์”ฉ ์ˆ˜์ •์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์ƒํƒœ๋ฅผ ๋” ์„ธ๋ถ„ํ™”ํ•˜๊ฑฐ๋‚˜, ๋น„๋™๊ธฐ ์ž‘์—…์„ ์œ„ํ•œ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์ฃ . ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ ์•„์ด๋””์–ด๋Š” ๊ฐ™์•„์š” - 'keyof'๋ฅผ ํ™œ์šฉํ•ด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ด๋Š” ๊ฑฐ์ฃ !

์–ด๋– ์„ธ์š”? ์ด๋Ÿฐ ์‹ค์ „ ์˜ˆ์ œ๋“ค์„ ๋ณด๋‹ˆ 'keyof'์˜ ํ™œ์šฉ ๊ฐ€๋Šฅ์„ฑ์ด ๋” ์™€๋‹ฟ์ง€ ์•Š๋‚˜์š”? 'keyof'๋Š” ๋‹จ์ˆœํžˆ ๊ฐ์ฒด์˜ ํ‚ค๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์„ ๋„˜์–ด์„œ, ํƒ€์ž… ์‹œ์Šคํ…œ ์ „๋ฐ˜์— ๊ฑธ์ณ ์•ˆ์ „์„ฑ๊ณผ ์œ ์—ฐ์„ฑ์„ ๋†’์ด๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ž๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ์—์„œ๋„ ์ด๋Ÿฐ ํŒจํ„ด๋“ค์„ ์ ์šฉํ•ด๋ณด์„ธ์š”. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์ด ํ•œ์ธต ๋” ๋†’์•„์งˆ ๊ฑฐ์˜ˆ์š”! ๐Ÿ˜‰

8. ๋งˆ๋ฌด๋ฆฌ: keyof์˜ ๋ฏธ๋ž˜์™€ ๋ฐœ์ „ ๋ฐฉํ–ฅ ๐Ÿ”ฎ

์ž, ์ด์ œ 'keyof'์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์ด ์•Œ์•„๋ณด์…จ์–ด์š”. ๊ธฐ๋ณธ ๊ฐœ๋…๋ถ€ํ„ฐ ์‹ค์ „ ์˜ˆ์ œ๊นŒ์ง€, 'keyof'์˜ ๋‹ค์–‘ํ•œ ๋ฉด๋ชจ๋ฅผ ์‚ดํŽด๋ดค์ฃ . ๊ทธ๋ ‡๋‹ค๋ฉด ์ด์ œ 'keyof'์˜ ๋ฏธ๋ž˜๋Š” ์–ด๋–จ๊นŒ์š”? TypeScript์™€ ํ•จ๊ป˜ ์–ด๋–ป๊ฒŒ ๋ฐœ์ „ํ•ด ๋‚˜๊ฐˆ์ง€ ํ•œ๋ฒˆ ์˜ˆ์ธกํ•ด๋ณผ๊นŒ์š”? ๐Ÿค”

1. ๋” ๊ฐ•๋ ฅํ•œ ํƒ€์ž… ์ถ”๋ก 

TypeScript ํŒ€์€ ๊ณ„์†ํ•ด์„œ ํƒ€์ž… ์ถ”๋ก  ๋Šฅ๋ ฅ์„ ๊ฐœ์„ ํ•˜๊ณ  ์žˆ์–ด์š”. ์•ž์œผ๋กœ 'keyof'์™€ ๊ด€๋ จ๋œ ํƒ€์ž… ์ถ”๋ก ๋„ ๋”์šฑ ๊ฐ•๋ ฅํ•ด์งˆ ๊ฑฐ์˜ˆ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ณต์žกํ•œ ์ œ๋„ค๋ฆญ ํƒ€์ž…์—์„œ๋„ 'keyof'์˜ ๊ฒฐ๊ณผ๋ฅผ ๋” ์ •ํ™•ํ•˜๊ฒŒ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฑฐ์˜ˆ์š”.

2. ์ƒˆ๋กœ์šด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…๊ณผ์˜ ํ†ตํ•ฉ

TypeScript์—๋Š” ๊ณ„์†ํ•ด์„œ ์ƒˆ๋กœ์šด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…๋“ค์ด ์ถ”๊ฐ€๋˜๊ณ  ์žˆ์–ด์š”. 'keyof'๋„ ์ด๋Ÿฐ ์ƒˆ๋กœ์šด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…๋“ค๊ณผ ๋” ์ž˜ ํ†ตํ•ฉ๋  ๊ฑฐ์˜ˆ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ์ฒด์˜ ์ค‘์ฒฉ๋œ ์†์„ฑ์„ ๋” ์‰ฝ๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ์ƒˆ๋กœ์šด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…์ด ๋‚˜์˜ฌ ์ˆ˜๋„ ์žˆ๊ฒ ์ฃ .

3. ๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ ์ตœ์ ํ™”

'keyof'๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์—๋งŒ ์ž‘๋™ํ•˜์ง€๋งŒ, ์ด๋ฅผ ํ™œ์šฉํ•œ ์ฝ”๋“œ ํŒจํ„ด๋“ค์ด ๋Ÿฐํƒ€์ž„์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ๋„ ๊ณ ๋ คํ•ด์•ผ ํ•ด์š”. TypeScript ํŒ€์€ ์ด๋Ÿฐ ํŒจํ„ด๋“ค์ด ๋” ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋„๋ก ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์ตœ์ ํ™”ํ•  ๊ฑฐ์˜ˆ์š”.

4. ๋” ๋„“์€ ์ƒํƒœ๊ณ„ ์ง€์›

'keyof'๋ฅผ ํ™œ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ๋” ๋งŽ์ด ๋‚˜์˜ฌ ๊ฑฐ์˜ˆ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ORM(Object-Relational Mapping) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ 'keyof'๋ฅผ ํ™œ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ TypeScript ํƒ€์ž…์„ ๋” ๊ธด๋ฐ€ํ•˜๊ฒŒ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๊ฒ ์ฃ .

5. ๋” ์ง๊ด€์ ์ธ ๋ฌธ๋ฒ•

TypeScript ํŒ€์€ ํ•ญ์ƒ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•˜๊ณ  ์žˆ์–ด์š”. 'keyof'์™€ ๊ด€๋ จ๋œ ๋ฌธ๋ฒ•๋„ ๋” ์ง๊ด€์ ์œผ๋กœ ๋ฐ”๋€” ์ˆ˜ ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ค‘์ฒฉ๋œ ๊ฐ์ฒด์˜ ํ‚ค๋ฅผ ๋” ์‰ฝ๊ฒŒ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์ƒˆ๋กœ์šด ๋ฌธ๋ฒ•์ด ์ถ”๊ฐ€๋  ์ˆ˜๋„ ์žˆ๊ฒ ์ฃ .

๐Ÿš€ Future Vision: ์•ž์œผ๋กœ 'keyof'๋Š” ๋‹จ์ˆœํžˆ ๊ฐ์ฒด์˜ ํ‚ค๋ฅผ ๋‹ค๋ฃจ๋Š” ๋„๊ตฌ๋ฅผ ๋„˜์–ด์„œ, ํƒ€์ž… ์‹œ์Šคํ…œ ์ „๋ฐ˜์„ ์•„์šฐ๋ฅด๋Š” ํ•ต์‹ฌ ์š”์†Œ๊ฐ€ ๋  ๊ฑฐ์˜ˆ์š”. ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ์™€ ๊ด€๊ณ„๋ฅผ ๋” ์ •ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๊ณ , ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋” ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค„ ๊ฑฐ๋ž๋‹ˆ๋‹ค. TypeScript์™€ ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” 'keyof'์˜ ๋ฏธ๋ž˜๊ฐ€ ์ •๋ง ๊ธฐ๋Œ€๋˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜Š

์ž, ์ด๋ ‡๊ฒŒ 'keyof'์˜ ํ˜„์žฌ์™€ ๋ฏธ๋ž˜์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. 'keyof'๋Š” TypeScript์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋กœ, ์•ž์œผ๋กœ๋„ ๊ณ„์†ํ•ด์„œ ๋ฐœ์ „ํ•ด ๋‚˜๊ฐˆ ๊ฑฐ์˜ˆ์š”. ์—ฌ๋Ÿฌ๋ถ„๋„ ์ด๋Ÿฐ ๋ฐœ์ „ ๊ณผ์ •์— ์ฃผ๋ชฉํ•˜๋ฉด์„œ, 'keyof'๋ฅผ ๋” ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ„์† ๊ณ ๋ฏผํ•ด๋ณด์„ธ์š”. ๊ทธ๋Ÿผ ์—ฌ๋Ÿฌ๋ถ„์˜ TypeScript ์ฝ”๋“œ๋Š” ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ณ  ์œ ์—ฐํ•ด์งˆ ๊ฑฐ์˜ˆ์š”!

์ด์ œ ์ •๋ง 'keyof'์— ๋Œ€ํ•œ ๋ชจ๋“  ๊ฒƒ์„ ์•Œ์•„๋ดค์–ด์š”. ๊ธฐ๋ณธ ๊ฐœ๋…๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•, ์‹ค์ „ ์˜ˆ์ œ, ๊ทธ๋ฆฌ๊ณ  ๋ฏธ๋ž˜ ์ „๋ง๊นŒ์ง€. ์—ฌ๋Ÿฌ๋ถ„์€ ์ด์ œ 'keyof'์˜ ์ง„์ •ํ•œ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋˜์…จ์–ด์š”! ๐Ÿ‘๐Ÿ‘๐Ÿ‘

TypeScript์™€ 'keyof'๋ฅผ ํ™œ์šฉํ•ด ๋” ๋‚˜์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋”ฉ ๋ผ์ดํ”„๊ฐ€ ํ•œ์ธต ๋” ์ฆ๊ฑฐ์›Œ์งˆ ๊ฑฐ์˜ˆ์š”. ํ–‰์šด์„ ๋น•๋‹ˆ๋‹ค! ๐Ÿ˜„