Защитник типа это проверка во время выполнения, которую понимает TypeScript, поэтому внутри проверенной ветки он сужает тип. typeof, instanceof и in это встроенные защитники; для всего остального вы пишете функцию, возвращаемый тип которой это предикат типа, value is Type.
Во время выполнения isUser возвращает обычный boolean. Возвращаемый тип value is User сообщает компилятору, что доказывает результат true, и каждое if (isUser(x)) затем сужает x до User.
Встроенные защитники типов
Эти проверки сужают тип без всяких вспомогательных функций:
| Защитник | Пример | Для чего |
|---|---|---|
typeof | typeof x === "number" | примитивы и функции |
instanceof | x instanceof Date | экземпляры классов |
in | "email" in x | объединения объектов, свойства объектов unknown |
Array.isArray | Array.isArray(x) | массивы |
| Равенство | x === null, x.kind === "circle" | null/undefined, литеральные метки |
| Истинность | if (x) | удаление null и undefined |
Все они выполняются как обычный JavaScript. TypeScript добавляет сужение: читает проверку и подстраивает тип в каждой ветке. Полный список форм на странице сужение типов. Собственный защитник нужен для проверок, которые не умещаются в одно выражение или которые хочется переиспользовать.
Как написать предикат типа
Предикат типа имеет вид parameterName is Type и заменяет boolean в качестве возвращаемого типа. Сужение работает в обе стороны: true сужает до Type, а false убирает Type из объединения.
Если передать защитник в filter, получится правильно типизированный массив. Начиная с TypeScript 5.5 компилятор выводит предикат и из простых стрелочных функций, поэтому pets.filter((p) => p.kind === "cat") возвращает Cat[] без именованного защитника.
Тип предиката должен подходить к типу параметра: function f(x: string): x is number даёт ошибку TS2677, A type predicate's type must be assignable to its parameter's type.
Компилятор доверяет вашему защитнику
TypeScript проверяет, что защитник возвращает boolean. Он не проверяет, что этот boolean верен. Защитник, который возвращает true для неправильных значений, заставляет типы лгать, и программа падает во время выполнения без всякой ошибки компиляции.
data.price.toFixed(2) бросает TypeError: Cannot read properties of undefined (reading 'toFixed') во время выполнения. Компилятор принял data.price как number, потому что так сказал защитник. Проверяйте каждое свойство, на которое опирается остальной код, и держите защитники маленькими, протестированными и рядом с типом, который они описывают.
Как проверить, что объект относится к типу
Это вопрос, который стоит за большинством собственных защитников: данные приходят как unknown (из JSON.parse, fetch, localStorage, сообщения), и нужно узнать, соответствуют ли они вашему интерфейсу. Рецепт:
typeof value === "object" && value !== null(объект, а неnull)."prop" in valueдля каждого обязательного свойства. Дляunknownоператорinдобавляет свойство в тип какunknown.typeof value.prop === "..."(или вложенный защитник) для типа каждого свойства.Array.isArray(value.items) && value.items.every(isItem)для массивов.
Для больших или глубоко вложенных форм писать такое вручную утомительно. Библиотеки схем вроде Zod или Valibot позволяют описать форму один раз и дают и проверку во время выполнения, и тип TypeScript.
Функции утверждения: asserts value is Type
Функция утверждения бросает исключение, если проверка не прошла, и обычно возвращает управление в остальных случаях. Её возвращаемый тип asserts value is Type (или asserts condition), и всё, что идёт после вызова, сужено без всякого if.
Одно правило часто сбивает с толку: функцию утверждения нужно вызывать через имя с явным типом. Стрелочная функция в const без аннотации, const check = (v: unknown): asserts v is string => {...}, даёт в месте вызова ошибку TS2775, Assertions require every name in the call target to be declared with an explicit type annotation. Используйте объявление function или аннотируйте константу типом функции.
Защитники, утверждения и приведения
| Инструмент | Проверка во время выполнения? | Сужает | При неудаче |
|---|---|---|---|
Встроенный защитник (typeof, in...) | Да | внутри ветки | идёт в другую ветку |
Функция value is T | Да (ваш код) | внутри ветки | идёт в другую ветку |
Функция asserts value is T | Да (ваш код) | после вызова | бросает исключение |
value as T | Нет | выражение | ничего: неверный тип распространяется дальше |
Утверждение типа (as) меняет тип, ничего не проверяя. На границе, где данные приходят извне, защитник или функция утверждения это безопасный вариант той же идеи.
Защитники на основе this в классах
Метод может сужать объект, на котором он вызван, через this is Type. Это удобно в иерархиях классов:
class FileNode {
constructor(public name: string) {}
isDirectory(): this is DirectoryNode {
return this instanceof DirectoryNode;
}
}
class DirectoryNode extends FileNode {
children: FileNode[] = [];
}
function count(node: FileNode): number {
return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}
Часто задаваемые вопросы
Что такое защитник типа в TypeScript?
Любая проверка во время выполнения, по которой TypeScript сужает тип: typeof x === "string", x instanceof Date, "id" in x, Array.isArray(x) или вызов функции, возвращаемый тип которой это предикат типа, например x is User. Внутри проверенной ветки переменная имеет более узкий тип.
Как проверить, что объект относится к типу, в TypeScript?
Типов во время выполнения не существует, поэтому проверяют свойства: напишите функцию isUser(value: unknown): value is User, которая проверяет typeof value === "object", value !== null и каждое обязательное свойство через in и typeof. После if (isUser(x)) переменная x имеет тип User. Для классов достаточно x instanceof MyClass.
Что означает «value is Type» в TypeScript?
Это предикат типа, который используется как возвращаемый тип функции. Во время выполнения функция по-прежнему возвращает boolean, но когда она возвращает true, TypeScript в месте вызова сужает аргумент до Type, а когда false, сужает его до остальных членов объединения. Компилятор не проверяет тело функции, поэтому проверка обязана быть правильной.
Чем защитник типа отличается от функции утверждения?
Защитник типа (x is T) возвращает boolean и сужает внутри if. Функция утверждения (asserts x is T) ничего не возвращает и бросает исключение, если проверка не прошла, поэтому всё после вызова сужено без всякого if. Используйте защитники для ветвления, а утверждения для случаев «это обязано выполняться, иначе остановиться».
Можно ли проверить, что объект реализует интерфейс, в TypeScript?
Напрямую нельзя: интерфейсы стираются, а instanceof их не принимает. Напишите защитник типа, который проверяет свойства интерфейса, или добавьте литеральное свойство-метку (kind: "user") и сравнивайте его. Библиотеки схем вроде Zod генерируют и проверку, и тип из одного описания.