Сужение типов
“Что это за монстр такой?” — спросите Вы, сейчас я попробую Вам ответить на этот вопрос
Примеры
Примечание:
Сужать типы можно с помощью typeof, instanceof, Array.isArray, isNaN:
typeof
Возможные варианты typeof
stringnumberbigintbooleansymbolundefinedobjectfunction
Давайте представим, что у нас есть функция, которая может принимать
const print = (data: string | string[]) => { /* empty */ };Окей, хорошо! Теперь нам нужно как-то определить, что нам поступает, строка или массив.
Мы можем сделать это через typeof, который определяет тип (но с небольшим но)
const print = (data: string | string[]) => {
if (typeof data === "string") {
return data;
} else {
const output = data.join("\n");
return output;
}
}Ну или можем сделать попроще:
const print = (data: string | string[]) => {
const output = typeof data === "string"
? data
: data.join("\n");
return output;
};Что здесь в итоге происходит? На деле всё просто: мы ограничиваем исходный тип какими-то другими. Разберем нашу функцию:
она принимает data с типом string|string[], а значит мы не можем знать наверняка, какой тип нам придёт, однако нам нужно как-то это узнать
тут-то и помогает сужение типов: мы сами определяем, с каким типом мы хотим работать
instanceof
type MyError = string | string[] | Error;
const printError = (error: MyError): string => {
if (error instanceof Error) {
return error.stack ?? error.message;
} else if (typeof error === "string") {
return error
} else {
return error.join("\n");
}
}Как работает instanceof? Он работает только с объектами (не с типами), для этого он и нужен — typeof работает только для встроенных типов
(см. возможные варианты typeof)
С помощью instanceof мы можем определить свои объекты, которые можем использовать для определения типа данного нам объекта
Array.isArray
type MaybeArray<T> = T | [T, ...T[]];
const validateInput = <T>(data: MaybeArray<T>): {
elements: T[],
length: number,
text: string
} => {
if (Array.isArray(data)) {
return {
elements: data,
length: data.length,
text: "Получен массив"
};
} else {
return {
elements: [data],
length: 1,
text: "Получен одиночный элемент"
};
}
};Array.isArray помогает нам понять, дали ли нам массив или нет, так как typeof определять массив как object.
Тут, думаю, больше нечего рассказывать
isNaN
type MaybeNumber = string | number;
const calculateTotal = ({ price, quantity, currency }: { price: MaybeNumber, quantity: MaybeNumber, currency: string }): string => {
const numericPrice = +price;
const numericQuantity = +quantity;
const isNotNumber = isNaN(numericPrice) || isNaN(numericQuantity)
if (isNotNumber) {
throw new Error("Цена и количество должны быть числами");
};
const isPositive = numericPrice <= 0 || numericQuantity <= 0;
if (isPositive) {
throw new Error("Цена и количество должны быть положительными");
};
const total = numericPrice * numericQuantity;
const text = `Общая стоимость: ${total.toFixed(2)} ${currency}.`;
return text;
};isNaN просто проверяет, не является ли число не числом (если мы перевели строку в число, то оно может быть NaN, но NaN относиться к типу number)
Небольшое НО:
Как Вы заметили, здесь нет типа array, потому что он помечается как object. Чтобы узнать достоверно,
является ли сущность массивом, нужно исопльзовать встроенные метод в JavaScript: Array.isArray
Он вернёт true, если сущность является массивом, иначе false
console.log(Array.isArray(["1", "2"])) // true
console.log(Array.isArray(null)) // false
console.log(Array.isArray(undefined)) // false
console.log(Array.isArray({ hello: [] })) // false
console.log(Array.isArray("str")) // false