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