TypeScript satisfies: когда вместо as
Оператор satisfies в TypeScript появился в 4.9 и уже к 2026 году стал привычным. Но тренд я наблюдаю до сих пор: разработчики, особенно из мира JS, путают satisfies с as и иногда применяют их вперемешку. Это два разных инструмента, и используются они в разных ситуациях. Покажу, когда какой и почему.
В чём разница в одном предложении
Оператор as утверждает «считай это значение принадлежащим типу X», даже если ты не прав. Оператор satisfies проверяет «соответствует ли это значение типу X», но сохраняет точный исходный тип значения.
На примере:
type Colors = {
primary: string;
secondary: string;
};
// 1. Без аннотации — широкий тип
const a = {
primary: '#ff0000',
secondary: '#00ff00',
};
// a имеет тип { primary: string; secondary: string }
// 2. С as — обещание, без проверки расширений
const b = {
primary: '#ff0000',
secondary: '#00ff00',
} as Colors;
// b: Colors
// 3. С аннотацией — теряем точные значения
const c: Colors = {
primary: '#ff0000',
secondary: '#00ff00',
};
// c: Colors (тоже широкий тип)
// 4. С satisfies — проверка + сохранение литералов
const d = {
primary: '#ff0000',
secondary: '#00ff00',
} satisfies Colors;
// d: { primary: '#ff0000'; secondary: '#00ff00' }Разница в результирующем типе. Если позже мне нужно typeof d.primary — я получу литерал '#ff0000', а не string. Это даёт точнее автокомплит и более строгие проверки в дальнейшем коде.
Когда нужен satisfies
Конфигурация с константами
Самый частый кейс. Я держу глобальные константы и хочу, чтобы автокомплит знал точные значения, но при этом проверял структуру:
type Routes = Record<string, { path: string; auth: boolean }>;
export const routes = {
home: { path: '/', auth: false },
profile: { path: '/profile', auth: true },
admin: { path: '/admin', auth: true },
} satisfies Routes;
// дальше:
type RouteName = keyof typeof routes; // 'home' | 'profile' | 'admin'
const path = routes.home.path; // тип '/'Без satisfies я получил бы RouteName = string и routes.home.path = string. С satisfies — узкие литеральные типы, и автокомплит подсказывает все три имени маршрута.
Дискриминированные объекты
Если у тебя объекты с union-полями, satisfies сохраняет точный вариант:
type Action =
| { type: 'add'; payload: number }
| { type: 'remove'; id: string };
const addAction = {
type: 'add' as const,
payload: 5,
} satisfies Action;
// addAction: { type: 'add'; payload: 5 } — точный вариант, не ActionБез satisfies переменная была бы либо широким Action, либо требовала бы кастов через as.
Маппинги
const handlers = {
click: (e: MouseEvent) => console.log(e.x),
keydown: (e: KeyboardEvent) => console.log(e.key),
} satisfies Record<string, (e: any) => void>;
handlers.click; // (e: MouseEvent) => void — точная сигнатура сохраниласьКогда нужен as
Когда ты уверен, что значение соответствует типу, но компилятор этого не может проверить. Самый частый случай — данные из JSON.parse, ответы API, преобразования событий.
// JSON.parse возвращает any
const data = JSON.parse(rawJson) as User;
// dataset значения — string, но мы знаем, что там id
const id = element.dataset.id as string;
// сужаем тип события
const input = e.target as HTMLInputElement;В этих случаях satisfies не подойдёт — компилятор не поверит в широкое преобразование. Здесь нужен явный каст.
const-аналогия
Когда хочется зафиксировать литералы целиком, можно использовать as const:
const colors = ['red', 'green', 'blue'] as const;
// colors: readonly ['red', 'green', 'blue']Это пересекается с satisfies, но решает другую задачу: as const делает всё readonly и фиксирует литералы. satisfies ничего не делает readonly, но проверяет соответствие типу.
const config = {
endpoint: 'https://api.example.com',
timeout: 5000,
} as const satisfies { endpoint: string; timeout: number };
// config — readonly, и при этом проверен на структуруСочетание as const satisfies я использую часто.
Когда satisfies не работает
Он не приводит тип. Если у тебя any или unknown, satisfies не превратит их в нужный тип:
const raw: unknown = JSON.parse(text);
const user = raw satisfies User; // ошибка компиляции — unknown не совместим с UserВ таких случаях нужен as или явная функция-валидатор (zod, valibot).
Где я ловил баги с as
Скрытые ошибки
type User = { id: string; name: string };
const user = {
id: '1',
// забыли name
} as User;
// компилятор молчит, name = undefined в рантаймеС satisfies:
const user = {
id: '1',
} satisfies User;
// ошибка: свойство 'name' отсутствуетsatisfies ловит на компиляции, as — пропускает.
Опечатки
type Config = { port: number };
const c = { port: '3000' } as Config; // молчит, в рантайме строка
const d = { port: '3000' } satisfies Config; // ошибка: string не совместим с numberЯ бы советовал всегда предпочитать satisfies, кроме случаев, когда данные приходят извне и требуют каста.
satisfies + дженерики
В функциях, которые возвращают разные типы в зависимости от аргументов, satisfies позволяет автору сохранить точный тип:
function defineRoutes<T extends Record<string, { path: string }>>(routes: T): T {
return routes;
}
const routes = defineRoutes({
home: { path: '/' },
profile: { path: '/me' },
});
// routes имеет точный тип всех ключейДо satisfies такие конструкции были стандартом. Сейчас часто проще написать без обёртки:
const routes = {
home: { path: '/' },
profile: { path: '/me' },
} satisfies Record<string, { path: string }>;Без вспомогательной функции, без обёрток, всё проверено.
Проверки во время рантайма
Ни satisfies, ни as не дают рантайм-проверок. Это инструменты компилятора. Если данные приходят из API — добавляй zod или valibot для валидации:
import { z } from 'zod';
const UserSchema = z.object({ id: z.string(), name: z.string() });
const raw: unknown = await response.json();
const user = UserSchema.parse(raw); // бросит, если форма невернаяТолько так у тебя есть гарантия, что в рантайме структура правильная.
В сухом остатке
- Используй
satisfiesдля констант, конфигов, маппингов, литералов — везде, где значение известно на этапе кода. - Используй
asтолько когда ты приводишь к более узкому типу изany,unknown,JSON.parseили подобных источников. - Сочетание
as const satisfiesхорошо работает для immutable-конфигурации с проверкой структуры. - Никогда не доверяй
asдля проверки структуры — это обещание, не валидация.
Когда я переключил команду на привычку «по умолчанию satisfies, as только в обоснованных случаях», количество багов с типами в ревью уменьшилось ощутимо. Это маленькая привычка, которая окупается за пару недель.