TypeScript 5.5 Satisfies Operator: Why You Should Use It Everywhere
The satisfies operator is TypeScript's most underused feature. Learn how it gives you the best of both worlds: type checking AND type inference, with practical examples you can use today.
If you've been writing TypeScript for a while, you've probably faced this dilemma: do you annotate your variable with a type (and lose inference), or skip the annotation (and lose validation)? The satisfies operator, stabilized in TypeScript 5.5, solves this elegantly — and most developers still aren't using it enough.
The Problem: Type Annotations Kill Inference
Consider a common pattern — defining a configuration object:
// With type annotation: you get validation but LOSE narrowed types
type Routes = Record<string, { path: string; auth: boolean }>;
const routes: Routes = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
profile: { path: "/profile", auth: true },
};
// routes.home exists? TypeScript doesn't know — it's just Record<string, ...>
// routes.home is typed as { path: string; auth: boolean } | undefined
routes.nonExistent; // No error! Any string key is valid
You validated the shape, but TypeScript forgot which keys actually exist. Now try accessing routes.home.path — you'll need a null check even though you know it's there.
Enter satisfies: Validate Without Losing Information
The satisfies operator checks that an expression matches a type without widening it:
const routes = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
profile: { path: "/profile", auth: true },
} satisfies Record<string, { path: string; auth: boolean }>;
// Now TypeScript knows EXACTLY which keys exist
routes.home.path; // ✅ "/" (literal type!)
routes.dashboard.auth; // ✅ true (literal type!)
routes.nonExistent; // ❌ Error: Property does not exist
// You get autocomplete for "home" | "dashboard" | "profile"
type RouteKey = keyof typeof routes; // "home" | "dashboard" | "profile"
This is the magic: you get full type validation and precise inference. The compiler checks your object matches Record<string, { path: string; auth: boolean }>, but the inferred type retains every literal key and value.
Real-World Patterns in Next.js
Here's where satisfies shines in production code. When building a Next.js app, theme configuration becomes type-safe without losing specificity:
// Theme tokens with full validation AND autocomplete
const theme = {
colors: {
primary: "#3b82f6",
secondary: "#64748b",
danger: "#ef4444",
},
spacing: {
sm: "0.5rem",
md: "1rem",
lg: "2rem",
},
} satisfies Record<string, Record<string, string>>;
// In your component:
function Alert({ variant }: { variant: keyof typeof theme.colors }) {
// variant is "primary" | "secondary" | "danger" — not just string
return <div style={{ color: theme.colors[variant] }} />;
}
Another powerful use case: API route handlers with validated response shapes:
type ApiHandler = {
GET?: () => Promise<Response>;
POST?: () => Promise<Response>;
};
export const { GET, POST } = {
GET: async () => {
const data = await fetchData();
return Response.json(data);
},
POST: async () => {
return Response.json({ created: true });
},
} satisfies ApiHandler;
When NOT to Use satisfies
Don't reach for satisfies when you genuinely want widening. If a function parameter should accept any string, annotating with : string is correct. satisfies is for when you want the compiler to validate structure while preserving the specific shape you wrote.
Key Takeaways
- Use
satisfiesinstead of type annotations when you need both validation and inference - It's ideal for config objects, route maps, theme tokens, and lookup tables
- Combine with
as const satisfies Tfor fully immutable, literal-typed configurations - Stop choosing between safety and developer experience —
satisfiesgives you both
Start replacing your Record annotations with satisfies today. Your future self (and your team's autocomplete) will thank you.
Admin
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!