Обновлено: июль 2026. Все примеры проверены на актуальной версии компилятора TypeScript.
Type guard (защитник типа) — это проверка, после которой TypeScript сужает тип переменной внутри блока кода. Пишете typeof x === "string" — и в ветке if компилятор считает x строкой, разрешая x.toUpperCase(). Сам процесс уточнения типа называется сужением (narrowing).
function len(x: string | string[]): number {
if (typeof x === "string") {
return x.length; // x: string
}
return x.length; // x: string[]
}
Коротко, что нужно знать:
- Встроенные guard’ы:
typeof,instanceof,inи проверка на истинность — покрывают большинство случаев. - Свой guard задаётся предикатом
arg is Type— функция возвращаетboolean, а компилятор трактует её как проверку типа. - Размеченные объединения (discriminated unions) сужают тип по общему полю-дискриминанту.
- Функции с
assertsсужают тип не через ветвление, а через факт, что вызов не бросил исключение. - Тип
neverвdefaultдаёт проверку на исчерпывающесть — компилятор напомнит про необработанный вариант.
Что такое type guard и зачем сужать тип
Переменная типа string | number — это объединение (union). Пока TypeScript не знает, какой из вариантов перед ним сейчас, он запрещает операции, доступные лишь одному из типов: у number нет .toUpperCase(), у string нет умножения. Type guard снимает эту неопределённость: проверка в рантайме сообщает компилятору, с каким конкретно типом он работает в данной ветке.
Механизм называется анализом потока управления (control flow analysis). Компилятор читает код сверху вниз, отслеживает каждую проверку и на её основе пересчитывает тип переменной для каждой ветки. Поэтому в примере ниже во втором return тип уже сужен без единого приведения:
function padLeft(padding: number | string, input: string): string {
if (typeof padding === "number") {
return " ".repeat(padding) + input; // padding: number
}
return padding + input; // padding: string
}
Мини-вывод. Type guard не меняет значение в рантайме — он меняет представление компилятора о типе. Правильно расставленные guard’ы убирают приведения as и ловят ошибки на этапе сборки. Полный перечень механизмов сужения — в официальном разделе TypeScript Handbook: Narrowing.

- ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросети DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
- Где и как применять? Потестируем модель после установки на разных задачах
- Как дообучить модель под себя?
Встроенные type guard’ы
typeof — для примитивов
Оператор typeof возвращает одну из строк: "string", "number", "bigint", "boolean", "symbol", "undefined", "object" или "function". Подходит для примитивных типов. Важная ловушка из JavaScript: typeof null возвращает "object" — это историческая ошибка языка, из-за неё нельзя отличить null от объекта одним лишь typeof.
Проверка на истинность
Условие if (value) отсекает все ложные значения: 0, NaN, "", 0n, null и undefined. Удобно для отсечения null перед работой с объектом, но помните: пустая строка и ноль тоже ложны, и такая проверка их выбросит вместе с null.
function printAll(strs: string | string[] | null) {
if (strs && typeof strs === "object") {
for (const s of strs) console.log(s); // strs: string[]
}
}
Сравнение (equality)
Операторы ===, !==, == и != тоже сужают тип. Отдельно полезен нестрогий != null: он одним условием убирает и null, и undefined.
function multiply(value: number | null | undefined, factor: number) {
if (value != null) {
return value * factor; // value: number
}
return 0;
}
Оператор in — по наличию свойства
Оператор in проверяет, есть ли у объекта свойство с указанным именем, и сужает объединение по этому признаку:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) return animal.swim(); // animal: Fish
return animal.fly(); // animal: Bird
}
Нюанс: если свойство опциональное (swim?), тип с ним попадёт в обе ветки условия — in надёжен только для обязательных свойств.
instanceof — для экземпляров классов
Когда типы заданы классами, работает instanceof: он проверяет, находится ли объект в цепочке прототипов класса. Это классический «экземплярный» type guard.
class Animal {
makeSound() { console.log("Some generic sound"); }
}
class Dog extends Animal {
makeSound() { console.log("Bark"); }
}
class Cat extends Animal {
makeSound() { console.log("Meow"); }
}
function makeAnimalSound(animal: Animal) {
if (animal instanceof Dog) {
animal.makeSound(); // animal: Dog -> Bark
} else if (animal instanceof Cat) {
animal.makeSound(); // animal: Cat -> Meow
} else {
animal.makeSound(); // animal: Animal
}
}
Мини-вывод. typeof — для примитивов, instanceof — для экземпляров классов, in — для объектных литералов без классов. Проверка на истинность и сравнение отсекают null и undefined.
Какой type guard выбрать: сравнение

| Guard | Что проверяет | Когда применять | Ограничение |
|---|---|---|---|
typeof |
Примитивный тип значения | string / number / boolean / symbol / bigint | typeof null === "object" |
| истинность | Truthy / falsy | быстро отсечь null и undefined |
выбрасывает 0, "", NaN |
=== / != null |
Равенство значений | литералы, отсечение null+undefined |
только по значению |
in |
Наличие свойства | объединения объектных литералов | опциональные свойства в обеих ветках |
instanceof |
Цепочку прототипов | объекты, созданные через классы | не работает для интерфейсов и литералов |
предикат arg is T |
Произвольную логику | сложная проверка, переиспользование | корректность на совести автора |
дискриминант kind |
Литеральное поле-метку | размеченные объединения | нужно завести общее поле |
Пользовательские type guard: предикаты arg is Type
Встроенных проверок хватает не всегда. Свой guard объявляется через предикат типа — в сигнатуре функции вместо boolean указывается parameterName is Type. Если функция вернула true, компилятор сужает тип аргумента до указанного:
interface Bird { fly(): void; }
interface Fish { swim(): void; }
function isBird(pet: Bird | Fish): pet is Bird {
return (pet as Bird).fly !== undefined;
}
function move(pet: Bird | Fish) {
if (isBird(pet)) {
pet.fly(); // pet: Bird
} else {
pet.swim(); // pet: Fish
}
}
Предикаты работают и при фильтрации массивов — filter с guard’ом возвращает сужённый тип элементов, а не исходное объединение:
const zoo: (Bird | Fish)[] = getAnimals();
const birds: Bird[] = zoo.filter(isBird); // тип сохранён
Ответственность за корректность здесь на разработчике: если тело функции вернёт true не для того типа, компилятор поверит на слово и ошибка проявится в рантайме.
Размеченные объединения и проверка на исчерпывающесть
Размеченное объединение (discriminated union) — это когда у всех членов union есть общее поле с литеральным типом-меткой. По нему switch сужает тип автоматически, без instanceof и приведений:
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; sideLength: number; }
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2; // shape: Circle
case "square": return shape.sideLength ** 2; // shape: Square
default:
const _exhaustive: never = shape; // страховка на будущее
return _exhaustive;
}
}
Поле kind здесь — дискриминант. Приём с never в default — это проверка на исчерпывающесть. В never нельзя присвоить никакой тип, кроме самого never. Пока обработаны все варианты, shape в default имеет тип never и присваивание проходит. Добавьте в union новый вариант Triangle — и компилятор тут же подсветит ошибку «Type ‘Triangle’ is not assignable to type ‘never’», напоминая дописать ветку. Это дешёвая страховка от забытого случая при росте кода.
Мини-вывод. Дискриминант удобнее instanceof, когда данные приходят как обычные объекты (например из JSON) — классов там нет, а поле-метка есть.
Функции-утверждения: asserts
Начиная с TypeScript 3.7 есть ещё один способ сужения — сигнатуры-утверждения (assertion signatures). Такая функция не возвращает boolean, а бросает исключение, если условие не выполнено. Сам факт, что вызов не прервал выполнение, сужает тип для всего оставшегося кода. Форм две.
asserts condition — гарантирует, что условие истинно дальше по коду:
function assert(condition: any, msg?: string): asserts condition {
if (!condition) throw new Error(msg);
}
function yell(str: unknown) {
assert(typeof str === "string");
return str.toUpperCase(); // str: string
}
asserts val is Type — утверждает конкретный тип аргумента:
function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
if (val === undefined || val === null) {
throw new Error(`Expected value to be defined, got ${val}`);
}
}
В отличие от предиката is, который сужает тип только внутри своей ветки, asserts-функция сужает тип линейно — от строки вызова и до конца области видимости. Удобно для входной валидации, где невалидные данные должны сразу прерывать выполнение.
Практический пример: разбор ответа API
Данные из сети приходят как unknown или any — доверять их форме нельзя. Пользовательский guard проверяет структуру один раз и дальше отдаёт типизированное значение:
interface User {
id: number;
name: string;
}
function isUser(data: unknown): data is User {
return (
typeof data === "object" &&
data !== null &&
"id" in data && typeof (data as any).id === "number" &&
"name" in data && typeof (data as any).name === "string"
);
}
async function loadUser(url: string): Promise<User | null> {
const data: unknown = await fetch(url).then((r) => r.json());
if (isUser(data)) {
return data; // data: User — можно обращаться к полям безопасно
}
console.warn("Ответ API не соответствует типу User");
return null;
}
Здесь guard закрывает разрыв между «что обещает тип» и «что реально пришло». Без такой проверки одно приведение as User замаскировало бы битый ответ и уронило бы код при первом обращении к полю.
Частые вопросы
Чем type guard отличается от приведения типа (as)?
Приведение as — это обещание компилятору «поверь, тут такой тип», без проверки в рантайме. Type guard реально проверяет значение во время выполнения, поэтому безопаснее: неверные данные будут пойманы, а не замаскированы.
Работает ли instanceof с интерфейсами?
Нет. Интерфейсы и типы-литералы стираются при компиляции, в рантайме их нет. instanceof опирается на прототип класса, поэтому применим только к объектам, созданным через class. Для интерфейсов используйте in или пользовательский предикат.
Что такое narrowing простыми словами?
Это когда компилятор из широкого типа (например string | number) выводит более узкий (string) на основании ваших проверок. Type guard — инструмент, который запускает сужение.
Когда брать discriminated union вместо instanceof?
Когда данные — это обычные объекты, а не экземпляры классов: ответы API, состояния редьюсера, конфиги. Общее поле-метка (kind, type, status) даёт сужение без классов и вдобавок проверку на исчерпывающесть через never.
Зачем нужен тип never в проверке?
never в default-ветке заставляет компилятор ругаться, если в объединении появился необработанный вариант. Это защищает от забытых случаев, когда через полгода в union добавят новый член.
- Освой нейросеть Perplexity и узнай, как пользоваться функционалом остальных ИИ в одном
- УЧАСТВОВАТЬ ЗА 0 РУБ.
- Расскажем, как получить подписку
- ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросеть DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ