๋ฐ˜์‘ํ˜•

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ 4.9 ๋ฒ„์ „๋ถ€ํ„ฐ ์•ˆ์ „ํ•œ *์—…์บ์ŠคํŒ…์„ ์ง€์›ํ•˜๋Š” satisfies ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. satisfies ์—ฐ์‚ฐ์ž๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํƒ€์ž…์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ ๋„ ๋ณ€์ˆ˜์— *์ œ์•ฝ ์กฐ๊ฑด(constraint)์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ์— ์ œ์•ฝ ์กฐ๊ฑด์„ ์ ์šฉํ•˜๋ฉด์„œ ๊ฐ ํ”„๋กœํผํ‹ฐ์˜ ํƒ€์ž…์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ’ก ์šฉ์–ด ์„ค๋ช…

  • ์บ์ŠคํŒ… : ํƒ€์ž… ๋ณ€ํ™˜
  • ์—…์บ์ŠคํŒ… : ์ž์‹ ํด๋ž˜์Šค๋ฅผ ๋ถ€๋ชจ ํด๋ž˜์Šค ํƒ€์ž…์œผ๋กœ ์บ์ŠคํŒ… (์ƒ์œ„ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜)
  • ๋‹ค์šด์บ์ŠคํŒ… : ๋ถ€๋ชจ ํด๋ž˜์Šค๋ฅผ ์ž์‹ ํด๋ž˜์Šค ํƒ€์ž…์œผ๋กœ ์บ์ŠคํŒ… (ํ•˜์œ„ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜)
  • ์ œ์•ฝ ์กฐ๊ฑด : ํŠน์ • ํƒ€์ž…์ด๋‚˜ ๊ตฌ์กฐ๋ฅผ ๊ฐ•์ œํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

 

๋ฌธ์ œ


์•„๋ž˜ ์ฐธ๊ฐ€์ž ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” attendee ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค. ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•˜๋ฉด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ๊ฐ์ฒด์˜ ๊ฐ ํ”„๋กœํผํ‹ฐ ํƒ€์ž…์„ ์ž๋™์œผ๋กœ ์ถ”๋ก ํ•œ๋‹ค.

 

๊ทธ๋ž˜์„œ attendee.age(number ํƒ€์ž…์œผ๋กœ ์ถ”๋ก )์— ์ˆซ์ž๋ฅผ ๋”ํ•˜๊ฑฐ๋‚˜ attendee.name(string ํƒ€์ž…์œผ๋กœ ์ถ”๋ก )์— toUpperCase ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ attendee ๊ฐ์ฒด๊ฐ€ ์–ด๋–ค ํ”„๋กœํผํ‹ฐ๋กœ ๊ตฌ์„ฑ๋ผ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ๋ช…์‹œํ•˜์ง€ ์•Š์•„์„œ(์ œ์•ฝ ์กฐ๊ฑด ์—†์Œ) posittion ๊ฐ™์€ ์˜คํƒ€๊ฐ€ ์ƒ๊ฒจ๋„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

// attendee: { name: string; age: number; posittion: string; }
const attendee = {
  name: 'Smith',
  age: 30,
  posittion: 'Engineer', // ์˜คํƒ€
};

const plusAge = attendee.age + 10; // OK (number ํƒ€์ž…์œผ๋กœ ์ถ”๋ก )
const nameToUpperCase = attendee.name.toUpperCase(); // OK (string ํƒ€์ž…์œผ๋กœ ์ถ”๋ก )

 

ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜(ํƒ€์ž… ํ‘œ๊ธฐ)์„ ์‚ฌ์šฉํ•˜๋ฉด ์œ„ ๊ฐ™์€ ์˜คํƒ€๋ฅผ ์‚ฌ์ „์— ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ์ฒด์˜ ๊ฐ ํ”„๋กœํผํ‹ฐ๊ฐ€ ์–ด๋–ค ํƒ€์ž…์„ ๊ฐ€์ง€๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์žƒ๊ฒŒ ๋œ๋‹ค. ๊ฒฐ๊ตญ attendee ๊ฐ์ฒด์˜ ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋Š” string | number ์œ ๋‹ˆ์˜จ ํƒ€์ž…์„ ๊ฐ–๊ฒŒ ๋œ๋‹ค.

type Attendee = 'name' | 'age' | 'position';

const attendee: Record<Attendee, string | number> = {
  name: 'Smith',
  age: 30,
  posittion: 'Engineer', // Error! ์˜คํƒ€ ๊ฐ์ง€
};

const plusAge = attendee.age + 10; // Error! ts(2365)
const nameToUpperCase = attendee.name.toUpperCase(); // Error! ts(2339)

 

๋•Œ๋ฌธ์— attendee.age์— 10์„ ๋”ํ•˜๋ฉด string | number ํƒ€์ž…์— + ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ๋‚˜์˜จ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ attendee.name์— toUpperCase๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด string | number ํƒ€์ž…์— toUpperCase ์†์„ฑ์ด ์—†๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

ํ•ด๊ฒฐ


์œ„ ๊ฐ™์€ ๋ฌธ์ œ๋Š” satisfies ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿผ attendee ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ๋ฅผ Attendee ํƒ€์ž…์œผ๋กœ ์ œํ•œํ•˜๋ฉด์„œ(์ œ์•ฝ ์กฐ๊ฑด ์ ์šฉ), ๊ฐ ํ”„๋กœํผํ‹ฐ์˜ ํƒ€์ž…์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

type Attendee = 'name' | 'age' | 'position';

const attendee = {
  name: 'Smith',
  age: 30,
  posittion: 'Engineer', // Error! ์˜คํƒ€ ๊ฐ์ง€
} satisfies Record<Attendee, string | number>;

const plusAge = attendee.age + 10; // OK (number ํƒ€์ž…์œผ๋กœ ์ถ”๋ก )
const nameToUpperCase = attendee.name.toUpperCase(); // OK (string ํƒ€์ž…์œผ๋กœ ์ถ”๋ก )

 

์˜ˆ์‹œ — unknown

unknown ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋„ ํ”„๋กœํผํ‹ฐ ํƒ€์ž…์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๋ก ํ•œ๋‹ค. satisfies ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฐ ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์‚ฌ์šฉํ•  ๋•Œ๋งˆ๋‹ค ํƒ€์ž… ๋‹จ์–ธ/๊ฐ€๋“œ ์ž‘์—…์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

type Attendee = 'name' | 'age' | 'position';

const attendee = {
  name: 'Smith',
  age: 30,
  posittion: 'Engineer', // Error! ์˜คํƒ€ ์บ์น˜
} satisfies Record<Attendee, unknown>;

const plusAge = attendee.age + 10; // OK
const nameToUpperCase = attendee.name.toUpperCase(); // OK

 

 

์˜ˆ์‹œ — ํŠœํ”Œ

ํ”„๋กœํผํ‹ฐ ๊ฐ’์ด ํŠœํ”Œ(๋ฐฐ์—ด ๊ฐ ์š”์†Œ์˜ ํƒ€์ž…์ด ์ด๋ฏธ ์ •ํ•ด์ ธ ์žˆ๋Š” ํ˜•์‹)์ผ ๋•Œ๋„ ์ž˜ ์ž‘๋™ํ•œ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ RGB ํƒ€์ž…์€ [number, number, number] ์ง€๋งŒ, blue ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์€ [number, number] ํƒ€์ž…์ด๋ฏ€๋กœ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

// ์ฝ”๋“œ ์ฐธ๊ณ  via ๊ณต์‹ ๋ฌธ์„œ
type RGB = [red: number, green: number, blue: number];

const palette = {
  red: [255, 0, 0],
  green: '#00ff00',
  blue: [0, 0], // Error! ts(2322)
} satisfies Record<string, string | RGB>;

const redComponent = palette.red.at(0); // OK
const greenNormalized = palette.green.toUpperCase(); // OK

 

์˜ˆ์‹œ — as const ํ‚ค์›Œ๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ

satisfies ์—ฐ์‚ฐ์ž๋Š” as const ํ‚ค์›Œ๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. as const ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด์˜ ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๊ฐ€ readonly๋กœ ๋ณ€๊ฒฝ๋˜๊ณ  ํ• ๋‹น๋œ ๋ฆฌํ„ฐ๋Ÿด ๊ฐ’(์†Œ์Šค ์ฝ”๋“œ์— ์ง์ ‘ ์ž…๋ ฅํ•œ ๊ณ ์ •๋œ ๊ฐ’)์œผ๋กœ ํ”„๋กœํผํ‹ฐ ํƒ€์ž…์„ ์ถ”๋ก ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ palette ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ ๊ฐ’์— ์ ์šฉํ•œ RGB ํƒ€์ž…์—๋„ readonly ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ™์—ฌ์ค˜์•ผ ํ•œ๋‹ค.

type RGB = readonly [red: number, green: number, blue: number];

const palette = {
  red: [255, 0, 0],
  green: '#00ff00',
  blue: [0, 0, 0],
} as const satisfies Record<string, string | RGB>;

 

as const ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด red ํ”„๋กœํผํ‹ฐ๋Š” [number, number, number] ํƒ€์ž…์„ ๊ฐ€์ง„๋‹ค

 

์˜ˆ์‹œ — as Type vs satisfies ๋น„๊ต

type Action = {
  label: string;
  onClick?: () => void;
  color?: 'primary';
  variant?: 'outlined' | 'contained';
};

const actions = [
  { label: 'No', onClick: closeModal, variant: 'outlined' },
  { label: 'Yes', onClick: onConfirm, variant: 'contained' },
] satisfies Action[];
  • as Action[] ํƒ€์ž… ๋‹จ์–ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์— ์˜คํƒ€(variants ๋“ฑ)๊ฐ€ ์žˆ์–ด๋„ ์žก์•„๋‚ด์ง€ ๋ชปํ•œ๋‹ค
  • ํƒ€์ž… ๋‹จ์–ธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ variant ํ”„๋กœํผํ‹ฐ ๊ฐ’ ํƒ€์ž…์„ ํ•ญ์ƒ string์œผ๋กœ ์ถ”๋ก ํ•œ๋‹ค
  • satisfies๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์˜ ์˜คํƒ€๋ฅผ ์žก์•„๋‚ด๊ณ , ๊ฐ ํ”„๋กœํผํ‹ฐ ๊ฐ’์˜ ํƒ€์ž… ์ •๋ณด๋„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•