Добрый вечер. Объясните, пожалуйста, подробнее про 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
.
Пример
Давайте напишем код, который выведет в консоль значение поля 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
- Типы данных в JavaScript. Инструкция для начинающих
- Живые и неживые коллекции
- Метод preventDefault