Добрый вечер. Объясните, пожалуйста, подробнее про type predicates в TS. В каких случаях их можно использовать. Например, когда к одному обработчику можно применить union type и в нем задать, например TouchEvent | MouseEvent. Для чего использовать in в type predicates? Спасибо.

Отвечает Игорь Антонов, автор курсов по JavaScript в Академии.

В JavaScript и TypeScript есть конструкции для проверки типа. Достаточно вспомнить ООП и оператор instanceof. Начнём немного издалека и посмотрим на такой код:

class Dog {}

class Cat {}

const keks = new Cat();
const buddy = new Dog();

function isCat(something) {
  if (something instanceof Cat) {
    return true;
  }
  return false;
}

Функция умеет отличать котов от собак, в ней объявлено два класса и функция isCat. Если аргументом передать keks, функция вернёт true. Для аргумента buddy результатом станет false. Проверка внутри функции реализована с помощью оператора instanceof.

Теперь вернёмся к TypeScript и взглянем на такой код:

type Dog = { 
  isBig: boolean;  name: string; 
}  

type Cat = {  
  lives: number;  name: string; 
}  

type Animal = Dog | Cat;  

function getAnimal(): Animal {
  return { isBig: true, name: "spike" };
}

Мы описали два типа (Dog и Cat), воспользовались типом объединения и вывели Animal. Ещё мы определи функцию getAnimal. Она позволяет получить объект для описания животного. Судя по форме объекта — это Dog.

💫 Узнайте больше о теории типов, научитесь на практике использовать аннотацию типов и обобщённое программирование на профессиональном курсе по TypeScript.

Пример

Давайте напишем код, который выведет в консоль значение поля lives для котов, и значение поля isBig для собак.

Сначала напишем функцию isCat. Чтобы в Animal отделить кошек от собак, проверим наличие того или иного поля. Если у есть поле lives. то значит это кот. Отразим это в функции. Она должна возвращать значение типа boolean.

Теоретически такая функция может выглядеть так:

function isCat(animal: Animal): boolean { 
  return (animal as Cat).lives !== undefined; 
} 

Одна, когда начнём ей пользоваться, получим ошибки, так как в animal могут быть и коты, и собаки. Попробуем добавить предикат. Установим его в качестве типа для возвращаемого значения функции:

function isCat(animal: Animal): animal is Cat { 
  return (animal as Cat).lives !== undefined; 
} 

animal is Cat — это предикат. Сигнатура строится из имени параметра функции animal и оператора is. Теперь попробуем воспользоваться этой функцией:

const animal = getAnimal();

if (isCat(animal)) {
  console.log(animal.lives);
} else {
  console.log(animal.isBig);
}

Если isCat вернёт true, то перед нами кот, соответственно, у него есть свойство lives. Попробуйте скопировать код в песочницу, а затем удалить имя свойства lives на строке, где происходит вывод в консоль. Поставьте точку и посмотрите список доступных полей. Вы увидите только lives и name. Это справедливо только для ветки if, так как TypeScript понимает, что если условие истинно, значит перед нами кот.

Попробуйте сделать то же самое внутри ветки else. В этот раз будут доступны только поля, которые есть у Dog. В контексте TS всё, что мы провернули, называется Type Guard.

Теперь по поводу in. Оператор тоже пригодится для сужения типа. Например, можно написать функцию isCat с его помощью:

function isCat(animal: Animal): animal is Cat {
  return 'lives' in animal;
}

Если в animal есть поле animal, значит это кот. Здесь, конечно, стоит добавить дополнительную проверку на undefined, но это уже детали.

Ещё немного о JavaScript