typescriptedit button

satisfies keyword in TypeScript 4.9

5 min read|23. 7. 17.

๐Ÿ‘‹ TypeScript 4.9 is coming.

Why

์–ด๋–ค ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š”์ง€๋Š” syntax๊ฐ€ ํƒ„์ƒํ•œ ๋ฐฐ๊ฒฝ์„ ํ†ตํ•ด ์‚ดํŽด๋ณด์ž.

Situation 1.

// ์—ฌ๊ธฐ palette๋ผ๋Š” tuple์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ ˆ์ฝ”๋“œ๋ผ๊ณ  ๋ถ€๋ฅด์ฃ .
const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  bleu: [0, 0, 255]
};

// red๋Š” `number[]` ์ด์–ด์•ผ ํ•œ๋‹ค.
const redValue = palette.red.join(',');

// green์€ `string` ์ด์–ด์•ผ ํ•œ๋‹ค.
const greenValue = palette.green.toUpperCase();

// blue๋„ `number[]` ์ด์–ด์•ผ ํ•œ๋‹ค.
const blueValue = palette.bleu.join(',');

ํ˜น์‹œ ์œ„ ์ฝ”๋“œ์˜ ๋ฌธ์ œ๋ฅผ ๋ˆˆ์น˜์ฑ„์…จ๋‚˜์š”?

bleu ๋ผ๋Š” ์˜คํƒ€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹คโ€ฆ! ใ…œใ…œ

  • key ๋กœ ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์ •ํ•ด์ ธ์žˆ๋‹ค.
  • ์–ด๋–ค ์ œ์•ฝ์ด ํ•„์š”ํ•˜๋‹ค.
  • ํƒ€์ž… ์ •์˜?!

(๋‹น์—ฐํ•œ ์ ‘๊ทผ) palette์— ํƒ€์ž…์ด ํ•„์š”ํ•˜๊ฒ ๊ตฐ.

type Color = "red" | "green" | "blue";

const palette: Record<Color, string | number[]> = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255], // Gooooood!!
};

์ด๋ ‡๊ฒŒ ์˜คํƒ€๋ฅผ ์žก์„ ์ˆ˜ ์žˆ์ง€! ๋งŒโ€ฆ


const redValue = palette.red.join(",");
// โŒ Error: red is `number[] | string`

const greenValue = palette.green.toUpperCase();
// โŒ Error: green is `number[] | string`

value ์— ๋Œ€ํ•œ type infer๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹คโ€ฆ ๐Ÿ˜ญ

๋‘๋‘ฅ

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

const redValue = palette.red.join(",");
// โœ… red is `number[]`

const greenValue = palette.green.toUpperCase();
// โœ… green is `string`

One more thing โ˜๏ธ

const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255],
} satisfies Record<Color, unknown>; // ๐ŸŒ we don't need to specific type

const redValue = palette.red.join(","); // `red` is number[]
const greenValue = palette.green.toUpperCase(); // `green` is string

with as const

const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255],
} as const satisfies Record<Color, unknown>; // ๐ŸŒ we don't need to specific type

const redValue = palette.red.join(","); // `red` is [255, 0, 0]
const greenValue = palette.green.toUpperCase(); // `green` is "#00ff00"

๊ฐœ๊ฟ€.

Situation 2.

type Animal = {
  kind: 'dog' | 'cat';
  food: 'fish' | 'meat' | 'chur';
  age: number;
}
const puppy = { kind: 'dog', food: 'meat', age: 2 };

function calculateAge(animal: Animal) {
  return animal.age;
}

calculateAge(puppy);
// โŒ `puppy`๋Š” `Animal` ํƒ€์ž…๊ณผ ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
// { kind: string; food: string; age: number }

as โ€ฆ?

calculateAge(puppy as Animal);

๋Œ€ํ‘œ์ ์ธ ์•ˆํ‹ฐํŒจํ„ด.

puppy๊ฐ€ ๋ณ€ํ•ด๋„ ์•Œ ์ˆ˜๊ฐ€ ์—†๋‹ค.

as constโ€ฆ?

type Animal = {
  kind: 'dog' | 'cat';
  food: 'fish' | 'meat' | 'chur';
  age: number;
}
const puppy = {
	kind: 'dog' as const,
	food: 'meat' as const,
	age: 2,
};

function calculateAge(animal: Animal) {
  return animal.age;
}

calculateAge(puppy);

๋˜๊ธด ๋˜๋Š”๋ฐโ€ฆ ๊ท€์ฐฎ๋„คโ€ฆ

ํƒ€์ž… ์ •์˜โ€ฆ?

type Animal = {
  kind: 'dog' | 'cat';
  food: 'fish' | 'meat' | 'chur';
  age: number;
}
const puppy: Animal = {
	kind: 'dog',
	food: 'meat',
	age: 2,
};

function calculateAge(animal: Animal) {
  return animal.age;
}

calculateAge(puppy);

๋˜๊ธด ๋˜๋Š”๋ฐโ€ฆ

puppy.kind // 'dog' | 'cat'
// not 'dog'

narrowโ€ฆ more narrowโ€ฆ

๋‘๋‘ฅ

type Animal = {
  kind: 'dog' | 'cat';
  food: 'fish' | 'meat' | 'chur';
  age: number;
}
const puppy = { kind: 'dog', food: 'meat', age: 2 } satisfies Animal;

function calculateAge(animal: Animal) {
  return animal.age;
}

calculateAge(puppy);
typeof puppy.kind // 'dog'

์บฌ

์ •๋ฆฌ

satisfies ๋ผ๋Š” ์ƒˆ๋กœ์šด ๋ฌธ๋ฒ•์ด ์ƒ๊ฒผ๋Š”๋ฐ,

  1. Property Name Constraining, Fulfillment ๊ฐ์ฒด์˜ key๊ฐ’์„ ์ œํ•œํ•  ๋•Œ, ์ „๋ถ€ ์กด์žฌํ•˜๋Š”์ง€ ํŒŒ์•…ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  2. Infer property value type ๊ฐ์ฒด์˜ value๊ฐ’์„ inferํ•˜์—ฌ ํƒ€์ž…์œผ๋กœ ์ง€์ •ํ•  ๋•Œ, ์‚ฌ์šฉํ•œ๋‹ค.
  3. Safe upcast ํƒ€์ž…์„ ์•ˆ์ „ํ•˜๊ฒŒ upcast ์‹œ์ผœ์ค„ ๋•Œ, ์‚ฌ์šฉํ•œ๋‹ค.

TMI

  • implements ๊ฐ€ ๋  ๋ป”ํ•จ.

References