내 유형 주석의 대부분을 대체하고 내 모든 as를 만족합니다.

작성자

카테고리:

← 피드로
DEV Community · MSakai · 2026-08-05 개발(SW)

MSakai

You have a config object. You want two things at once:

  1. TypeScript should check it against a type
  2. TypeScript should remember the exact values you wrote

Before TS 4.9 you could have either one. Not both.

Annotating loses the detail

type Config = Record<string, string | number>

const config: Config = {
  host: 'localhost',
  port: 3000,
}

config.port.toFixed()  // Error: Property 'toFixed' does not exist on 'string | number'

Enter fullscreen mode Exit fullscreen mode

The annotation checked the object, then widened every value to string | number. You knew port was a number. The compiler no longer does.

Omitting the annotation loses the check

const config = {
  host: 'localhost',
  prot: 3000,       // typo, nobody notices
}

config.port.toFixed()  // Error: Property 'port' does not exist

Enter fullscreen mode Exit fullscreen mode

Now the types are precise but the typo went unchallenged until a call site failed, three files away.

as is worse than both

const config = {
  host: 'localhost',
  prot: 3000,
} as Config

Enter fullscreen mode Exit fullscreen mode

This compiles. as is an assertion: you are telling the compiler to stop reasoning and take your word for it. The typo survives to runtime, and you have removed the one mechanism that would have caught it.

satisfies does both

const config = {
  host: 'localhost',
  port: 3000,
} satisfies Config

config.port.toFixed()  // number
config.host.toUpperCase()  // string

Enter fullscreen mode Exit fullscreen mode

The expression is checked against Config, and the inferred type stays exactly what you wrote. Misspell port and you get an error at the object literal, where the mistake is.

Where it earns its keep

Route or event maps. Keys stay literal, so downstream code can be exhaustive:

const routes = {
  home: '/',
  profile: '/users/:id',
} satisfies Record<string, `/${string}`>

type RouteName = keyof typeof routes   // 'home' | 'profile'

Enter fullscreen mode Exit fullscreen mode

With an annotation, RouteName would have collapsed to string.

Theme tokens. You want the shape validated and the keys autocompleted:

const theme = {
  primary: '#0af',
  danger: '#f33',
} satisfies Record<string, `#${string}`>

Enter fullscreen mode Exit fullscreen mode

Discriminated unions in arrays. Each element is checked, and narrowing still works afterwards:

const handlers = [
  { kind: 'click', fn: (e: MouseEvent) => {} },
  { kind: 'key',   fn: (e: KeyboardEvent) => {} },
] satisfies Handler[]

Enter fullscreen mode Exit fullscreen mode

When to keep the annotation

satisfies is not a replacement for annotations everywhere. Keep the annotation when you want the wider type — usually on a let, or on something that gets reassigned:

let state: 'idle' | 'loading' | 'done' = 'idle'
state = 'loading'   // needs the annotation to be legal

Enter fullscreen mode Exit fullscreen mode

With satisfies here, state would be inferred as 'idle' and the reassignment would fail.

The rule

Annotate when you want the wider type. Use satisfies when you want the check without the widening. Use as when you have genuinely more information than the compiler — which is rarer than your codebase currently suggests.

Grep for as in your project. A good fraction of the hits are satisfies waiting to happen, and each one you convert is a check you get back.

These posts come out of material I build for my Udemy courses — 25 of them now, mostly drill-based, across Go, Python, TypeScript, testing and Three.js. If this was useful, the full list is at udemy-c1f90.web.app. The links on that page carry a coupon I refresh each month, which usually lands around half the list price.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다