value satisfies Type во время компиляции проверяет, что value соответствует Type, а затем оставляет собственный, более точный тип значения в покое. Аннотация заменила бы этот точный тип на Type; satisfies проверяет без расширения.
satisfies всё равно выполняет проверку: пропущенный цвет, ключ с опечаткой вроде bleu или значение вроде true дают ошибку компиляции на этой строке. Он существует начиная с TypeScript 4.9 и, как любая аннотация типа, удаляется из сгенерированного JavaScript.
Какую проблему решает satisfies
С аннотацией типа тип переменной и есть аннотация. Компилятор забывает, что видел в литерале. Здесь та же палитра аннотирована, и TypeScript больше не знает, что green это строка:
Компилятор сообщает:
index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
До TypeScript 4.9 выбор был такой: аннотировать и везде сужать вручную (typeof palette.green === "string") или отказаться от аннотации и потерять проверку. satisfies даёт и то, и другое. Замените : Record<ColorName, Color> на satisfies Record<ColorName, Color> после закрывающей скобки, и код запустится.
satisfies, аннотация типа и as
Один и тот же объект настроек, записанный тремя способами:
as пропустил отсутствующий lang, и asserted.lang во время выполнения равен undefined, хотя тип говорит string. Уберите lang из двух других строк, и обе упадут с TS2741, Property 'lang' is missing in type ....
Аннотация const x: T = v | Утверждение v as T | v satisfies T | |
|---|---|---|---|
| Отсутствующие свойства | ошибка | разрешены | ошибка |
| Лишние свойства (объектный литерал) | ошибка | разрешены | ошибка |
| Неверный тип свойства | ошибка | только если типы не пересекаются | ошибка |
Тип x после этого | T | T | выведенный тип v |
Литеральные типы ("dark", 8080) | расширяются до T | расширяются до T | сохраняются, где это позволяет T |
Ключи Record<string, ...> | любая строка (опечатки компилируются) | любая строка | ровно написанные ключи |
| Эффект во время выполнения | никакого | никакого | никакого |
Простое правило: аннотируйте, когда хотите, чтобы переменная имела объявленный тип (значение, которое будет переприсваиваться, публичный API), и используйте satisfies, когда нужна проверка, но собственный тип значения полезнее.
Ловля ошибок в объектных литералах
satisfies выполняет полную проверку присваиваемости, включая проверку лишних свойств, поэтому опечатки в ключах дают ошибки:
type Route = { path: string; method: "GET" | "POST" };
const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?
Проверка также даёт литералу контекстный тип, как это делает аннотация. Это важно в двух отношениях. Строковые литералы сохраняются как литеральные типы, когда их ожидает целевой тип: у { path: "/", method: "GET" } satisfies Route будет method: "GET", тогда как тот же объект без аннотации вывел бы method: string. А параметры колбэков выводятся из целевого типа:
Ключи Record остаются известными
Частое применение это таблица поиска. С аннотацией Record<string, T> любая строка считается допустимым ключом, и опечатка компилируется, возвращая undefined во время выполнения. С satisfies значения по-прежнему проверяются по T, но тип переменной перечисляет ровно те ключи, которые вы написали:
keyof typeof endpoints полезен только потому, что ключи сохранились. С аннотацией это был бы просто string.
Чтобы потребовать фиксированный набор ключей, используйте satisfies с Record по объединению: satisfies Record<"dev" | "prod", string> сообщает об отсутствующем prod ошибкой TS2741, а о неизвестном staging ошибкой TS2353.
as const satisfies
as const и satisfies сочетаются. Пишите as const первым: он делает значение глубоко readonly с литеральными типами, а затем satisfies проверяет это точное значение.
Каждый маршрут проверяется по Route (method: "PUT" дал бы ошибку), а кортеж литеральных типов остаётся доступен, поэтому Path это объединение реальных путей. В качестве цели используйте readonly Route[] (или ReadonlyArray<Route>), потому что массив с as const доступен только для чтения.
Объекты конфигурации
Конфигурация это то место, где satisfies особенно полезен: форма должна быть правильной, а код в других местах хочет точные значения.
Забудьте запись production, сделайте опечатку в logLevel или напишите logLevel: "verbose", и компилятор укажет точную строку. Тот же приём подходит для файлов *.config.ts: export default { ... } satisfies SomeConfig проверяет весь файл, а экспортируемый объект сохраняет свои литеральные значения.
Когда не стоит использовать satisfies
- Переменная будет переприсваиваться.
let cfg = { port: 3000 } satisfies { port: number | string }даётcfgтип{ port: number }, поэтому последующееcfg = { port: "80" }падает (TS2322). Переменные, которые вы собираетесь менять, аннотируйте. - Вам нужен именно объявленный тип. Для возвращаемого значения функции или экспортируемой константы, которая входит в API, тип аннотации и есть контракт, а утечка точного литерального типа может сделать последующие изменения ломающими.
- Значение не литерал.
satisfiesраскрывается на объектных и массивных литералах. Для переменной или результата вызова это обычная проверка присваиваемости, которую уже даёт аннотация.
Часто задаваемые вопросы
Что делает satisfies в TypeScript?
expression satisfies Type во время компиляции проверяет, что выражение можно присвоить Type, сообщая об отсутствующих свойствах, лишних свойствах и неверных типах значений, а затем оставляет собственный выведенный тип выражения без изменений. Вы получаете безопасность аннотации и точность вывода. Из JavaScript на выходе он стирается.
Чем satisfies отличается от аннотации типа?
Оба проверяют значение. Аннотация (const x: T = ...) затем даёт переменной тип T, забывая то, что компилятор знал о значении (литеральные типы, какой член объединения у каждого свойства, какие ключи существуют). satisfies T сохраняет выведенный тип, поэтому известно, что x.someKey существует, а свойство string | number, в котором лежит строка, имеет тип string.
Чем satisfies отличается от as в TypeScript?
as это утверждение: оно переопределяет тип и почти ничего не проверяет, так что отсутствующие свойства остаются незамеченными. satisfies это проверка: значение действительно должно соответствовать типу, а его собственный выведенный тип сохраняется. Когда компилируется и то, и другое, satisfies безопаснее.
Что означает as const satisfies?
Применяются оба: as const делает значение глубоко readonly с литеральными типами, а затем satisfies проверяет результат по типу. Пишите as const первым: const routes = [...] as const satisfies readonly Route[];. Переменная сохраняет точные литеральные типы для дальнейшего использования, а неверная запись по-прежнему даёт ошибку компиляции.
В какой версии TypeScript появился satisfies?
В TypeScript 4.9, вышедшем в ноябре 2022 года. Это обычный стираемый синтаксис, поэтому он работает и при встроенном удалении типов в Node, и его поддерживают все актуальные версии TypeScript (включая 7).