7.1 Основы Type Guards
Type Guards в TypeScript — это специальные конструкции, которые позволяют проверять типы значений во время выполнения. Они обеспечивают дополнительный уровень безопасности и помогают избежать ошибок, связанных с некорректным использованием типов.
Type Guards позволяют нам уточнять типы переменных в блоках кода, используя различные проверки. Это особенно полезно в тех случаях, когда переменная может иметь несколько типов, например, при использовании union types.
Рассмотрим простейший пример использования Type Guards:
"use strict";
function isString(value) {
return typeof value === "string";
}
function example(value) {
if (isString(value)) {
console.log(value.toUpperCase()); // TypeScript знает, что value — это string
}
else {
console.log(value.toFixed(2)); // TypeScript знает, что value — это number
}
}
example("hello"); // Вывод: HELLO
example(123.456); // Вывод: 123.46
function isString(value: any): value is string {
return typeof value === "string";
}
function example(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase()); // TypeScript знает, что value — это string
} else {
console.log(value.toFixed(2)); // TypeScript знает, что value — это number
}
}
example("hello"); // Вывод: HELLO
example(123.456); // Вывод: 123.46
Функция isString — это Type Guard, которая проверяет, является ли переданное значение строкой. Внутри функции example, после вызова isString, TypeScript точно знает тип переменной value и позволяет использовать методы, доступные только для этого типа.
Типы Type Guards
Существует несколько типов Type Guards, которые можно использовать в TypeScript:
- пользовательские Type Guards
- typeof
- instanceof
- in
8.2 Пользовательские Type Guards
Пользовательские Type Guards позволяют создавать функции, которые проверяют типы значений и возвращают типизированные утверждения.
Пример:
"use strict";
function isCat(animal) {
return animal.meow !== undefined;
}
function makeSound(animal) {
if (isCat(animal)) {
animal.meow();
}
else {
animal.bark();
}
}
const myCat = { meow: () => console.log("Meow") };
const myDog = { bark: () => console.log("Bark") };
makeSound(myCat); // Вывод: Meow
makeSound(myDog); // Вывод: Bark
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow();
} else {
animal.bark();
}
}
const myCat: Cat = { meow: () => console.log("Meow") };
const myDog: Dog = { bark: () => console.log("Bark") };
makeSound(myCat); // Вывод: Meow
makeSound(myDog); // Вывод: Bark
Здесь функция isCat проверяет, является ли животное кошкой, путем проверки наличия метода meow. Внутри функции makeSound TypeScript использует результат isCat для уточнения типа животного.
8.3 typeof
TypeScript предоставляет встроенный оператор typeof, который позволяет проверять тип примитивных значений (строк, чисел, boolean и т. д.).
Пример:
"use strict";
function example(value) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // TypeScript знает, что value — это string
}
else {
console.log(value.toFixed(2)); // TypeScript знает, что value — это number
}
}
example("hello"); // Вывод: HELLO
example(123.456); // Вывод: 123.46
function example(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // TypeScript знает, что value — это string
} else {
console.log(value.toFixed(2)); // TypeScript знает, что value — это number
}
}
example("hello"); // Вывод: HELLO
example(123.456); // Вывод: 123.46
В этом примере оператор typeof используется для проверки типа переменной value.
8.4 instanceof
Оператор instanceof используется для проверки того, является ли объект экземпляром определенного класса или его подкласса.
Пример:
"use strict";
class Cat {
meow() {
console.log("Meow");
}
}
class Dog {
bark() {
console.log("Bark");
}
}
function makeSound(animal) {
if (animal instanceof Cat) {
animal.meow();
}
else {
animal.bark();
}
}
const myCat = new Cat();
const myDog = new Dog();
makeSound(myCat); // Вывод: Meow
makeSound(myDog); // Вывод: Bark
class Cat {
meow() {
console.log("Meow");
}
}
class Dog {
bark() {
console.log("Bark");
}
}
function makeSound(animal: Cat | Dog) {
if (animal instanceof Cat) {
animal.meow();
} else {
animal.bark();
}
}
const myCat = new Cat();
const myDog = new Dog();
makeSound(myCat); // Вывод: Meow
makeSound(myDog); // Вывод: Bark
В этом примере оператор instanceof используется для проверки того, является ли объект экземпляром класса Cat.
8.5 in
Оператор in используется для проверки наличия свойства в объекте. Это полезно для проверки типов объектов, которые имеют разные свойства.
Пример:
"use strict";
function getAnimal() {
return Math.random() > 0.5
? {
fly: () => console.log("Bird is flying"),
layEggs: () => console.log("Bird is laying eggs")
}
: {
swim: () => console.log("Fish is swimming"),
layEggs: () => console.log("Fish is laying eggs")
};
}
function move(animal) {
if ("fly" in animal) {
animal.fly();
}
else {
animal.swim();
}
}
const animal = getAnimal();
move(animal); // В зависимости от случайного выбора, либо "Bird is flying", либо "Fish is swimming"
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function getAnimal(): Bird | Fish {
return Math.random() > 0.5
? {
fly: () => console.log("Bird is flying"),
layEggs: () => console.log("Bird is laying eggs")
}
: {
swim: () => console.log("Fish is swimming"),
layEggs: () => console.log("Fish is laying eggs")
};
}
function move(animal: Bird | Fish) {
if ("fly" in animal) {
animal.fly();
} else {
animal.swim();
}
}
const animal = getAnimal();
move(animal); // В зависимости от случайного выбора, либо "Bird is flying", либо "Fish is swimming"
Оператор in в этом примере используется для проверки наличия свойства fly в объекте animal. Так как функция getAnimal() возвращает объект, который может быть либо типа Bird, либо типа Fish, нам нужно определить, с каким конкретно объектом мы имеем дело, чтобы правильно вызвать его метод.
Конкретно в этом случае if ("fly" in animal) проверяет, есть ли у объекта animal свойство fly. Если это так, значит animal — птица (Bird), и мы можем вызвать метод fly(). Если такого свойства нет, значит, это рыба (Fish), и вызывается метод swim().
Таким образом, оператор in позволяет определить тип объекта на основе наличия конкретного метода, что важно для корректного выполнения соответствующей логики для разных типов животных.
8.6 Реальные примеры
Type Guards используются в реальных проектах для обеспечения типовой безопасности и предотвращения ошибок.
Пример 1: Обработка различных типов данных
"use strict";
function processData(data) {
if (typeof data === "string") {
console.log(`String data: ${data.toUpperCase()}`);
}
else if (typeof data === "number") {
console.log(`Number data: ${data.toFixed(2)}`);
}
else {
console.log(`Boolean data: ${data ? "True" : "False"}`);
}
}
processData("hello"); // Вывод: String data: HELLO
processData(123.456); // Вывод: Number data: 123.46
processData(true); // Вывод: Boolean data: True
type Data = string | number | boolean;
function processData(data: Data) {
if (typeof data === "string") {
console.log(`String data: ${data.toUpperCase()}`);
} else if (typeof data === "number") {
console.log(`Number data: ${data.toFixed(2)}`);
} else {
console.log(`Boolean data: ${data ? "True" : "False"}`);
}
}
processData("hello"); // Вывод: String data: HELLO
processData(123.456); // Вывод: Number data: 123.46
processData(true); // Вывод: Boolean data: True
Пример 2: Работа с различными типами сообщений
"use strict";
function handleMessage(message) {
switch (message.type) {
case "text":
console.log(`Text message: ${message.text}`);
break;
case "image":
console.log(`Image message: ${message.url}`);
break;
case "video":
console.log(`Video message: ${message.url}, duration: ${message.duration}`);
break;
}
}
handleMessage({ type: "text", text: "Hello, world!" }); // Вывод: Text message: Hello, world!
handleMessage({ type: "image", url: "http://example.com/image.jpg" }); // Вывод: Image message: http://example.com/image.jpg
handleMessage({ type: "video", url: "http://example.com/video.mp4", duration: 120 }); // Вывод: Video message: http://example.com/video.mp4, duration: 120
interface TextMessage {
type: "text";
text: string;
}
interface ImageMessage {
type: "image";
url: string;
}
interface VideoMessage {
type: "video";
url: string;
duration: number;
}
type Message = TextMessage | ImageMessage | VideoMessage;
function handleMessage(message: Message) {
switch (message.type) {
case "text":
console.log(`Text message: ${message.text}`);
break;
case "image":
console.log(`Image message: ${message.url}`);
break;
case "video":
console.log(`Video message: ${message.url}, duration: ${message.duration}`);
break;
}
}
handleMessage({ type: "text", text: "Hello, world!" }); // Вывод: Text message: Hello, world!
handleMessage({ type: "image", url: "http://example.com/image.jpg" }); // Вывод: Image message: http://example.com/image.jpg
handleMessage({ type: "video", url: "http://example.com/video.mp4", duration: 120 }); // Вывод: Video message: http://example.com/video.mp4, duration: 120
Преимущества использования Type Guards
- Повышенная типовая безопасность: обеспечивают строгую типизацию, помогая предотвратить ошибки на этапе компиляции.
- Гибкость: позволяют обрабатывать значения с различными типами, улучшая гибкость кода.
- Улучшенная читаемость и поддерживаемость: явное указание проверок типов делает код более понятным и легким для поддержки.
- Предотвращение ошибок времени выполнения: проверка типов во время выполнения помогает избежать ошибок, связанных с некорректным использованием типов.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ