TypeScript: Защитники типов (Type Guards)
Когда мы работаем с объединениями (union) в TypeScript, часто возникает необходимость совершить разные действия в зависимости от того, какой конкретно тип пришел.
Эта логика обычно реализуется с помощью разнообразных условий и проверок. Эти условия пишутся на JavaScript, но TypeScript умеет понимать некоторые из этих конструкций. То есть, если правильно написать проверку, то внутри блока if компилятор TypeScript будет понимать, что работает уже с уточненным типом.
Такие проверочные конструкции, которые понятны TS, называются «защитниками типа» - Type Guards. Они позволяют «сузить тип», выбрать нужный из объединения.
1. На основе типа данных
TypeScript понимает операторы
Эти операторы отлично распознаются компилятором внутри условий if-else, а также внутри тернарного оператора. Но не распознаются внутри конструкции switch.
2. С использование Tagged Union
Tagged Union (размеченное объединение) или Discriminated Union (дискриминантное объединение) - это объединение типов, у которых есть специальное общее поле со значением специфичным для конкретного типа. По этому полю эти типы можно различить.
Не имеет значения, как называется дискриминант, главное, чтобы он однозначно определял тип - TypeScript это понимает:
Такая проверка будет работать в любом условии: и в if-else, и в тернарном операторе, и даже в switch.
3. По наличию публичного поля
Почти то же самое, что и предыдущий пункт, но тут мы проверяем не значение поля, а его наличие - с помощью оператора
4. Функция-предикат
И наконец последняя (самая мощная) конструкция, которая может сузить объединение типов и которую может распознать TypeScript - это функция, которая возвращает предикат. Предикат - это однозначное утверждение, что значение, переданное этой функции, принадлежит к конкретному типу.
Такая функция оформляется особым образом - с помощью ключевого слова
Функция принимает параметр
Проверка внутри функции может быть абсолютно любая - TypeScript это не волнует. Нужно только, чтобы функция вернула логическое значение - true или false. Таким образом, ответственность за определение типа полностью перекладывается на разработчика.
Параметров тоже может быть сколько угодно, главное, чтобы один из них фигурировал в сигнатуре предиката.
#typescript
Когда мы работаем с объединениями (union) в TypeScript, часто возникает необходимость совершить разные действия в зависимости от того, какой конкретно тип пришел.
function move(obj: Dog | Fish | Bird) {
if (obj.type === ‘dog’) obj.run()
else if (obj.type === ‘fish’) obj.swim()
else obj.fly()
}
Эта логика обычно реализуется с помощью разнообразных условий и проверок. Эти условия пишутся на JavaScript, но TypeScript умеет понимать некоторые из этих конструкций. То есть, если правильно написать проверку, то внутри блока if компилятор TypeScript будет понимать, что работает уже с уточненным типом.
function move(obj: Dog | Fish | Bird) {
if (obj.type === ‘dog’) {
// Здесь TS уже уверен, что работает именно с Dog
obj.run()
}
// …
}
Такие проверочные конструкции, которые понятны TS, называются «защитниками типа» - Type Guards. Они позволяют «сузить тип», выбрать нужный из объединения.
1. На основе типа данных
TypeScript понимает операторы
instanceof
и typeof
.
function foo(param: number | string) {
if (typeof param === ‘string’) {
param.toUpperCase()
} else {
param.toFixed(2)
}
function move(obj: Dog | Fish | Bird) {
if (obj instanceof Dog) obj.run()
else if (obj instanceof Fish) obj.swim()
else obj.fly()
}
Эти операторы отлично распознаются компилятором внутри условий if-else, а также внутри тернарного оператора. Но не распознаются внутри конструкции switch.
2. С использование Tagged Union
Tagged Union (размеченное объединение) или Discriminated Union (дискриминантное объединение) - это объединение типов, у которых есть специальное общее поле со значением специфичным для конкретного типа. По этому полю эти типы можно различить.
class Bird {
type: ‘bird’ = ‘bird’
}
class Fish {
type: ‘fish’ = ‘fish’
}
Не имеет значения, как называется дискриминант, главное, чтобы он однозначно определял тип - TypeScript это понимает:
function move(obj: Dog | Fish | Bird) {
if (obj.type === ‘dog’) obj.run()
else if (obj.type === ‘fish’) obj.swim()
else obj.fly()
}
Такая проверка будет работать в любом условии: и в if-else, и в тернарном операторе, и даже в switch.
3. По наличию публичного поля
Почти то же самое, что и предыдущий пункт, но тут мы проверяем не значение поля, а его наличие - с помощью оператора
in
.
class Author {
public book: string = ‘Odyssey’
}
class Artist {
public painting: string = ‘Mona Lisa’
}
function getMasterpiece(creator: Author | Artist) {
let masterpiece;
if (‘book’ in creator) masterpiece = creator.book;
else masterpiece = creator.painting;
}
4. Функция-предикат
И наконец последняя (самая мощная) конструкция, которая может сузить объединение типов и которую может распознать TypeScript - это функция, которая возвращает предикат. Предикат - это однозначное утверждение, что значение, переданное этой функции, принадлежит к конкретному типу.
Такая функция оформляется особым образом - с помощью ключевого слова
is
:
function isDog(obj: Dog | Fish | Bird): obj is Dog {
return obj.type === ‘dog’
}
Функция принимает параметр
obj
и после проведения проверки утверждает, принадлежит ли obj
к типу Dog
.Проверка внутри функции может быть абсолютно любая - TypeScript это не волнует. Нужно только, чтобы функция вернула логическое значение - true или false. Таким образом, ответственность за определение типа полностью перекладывается на разработчика.
Параметров тоже может быть сколько угодно, главное, чтобы один из них фигурировал в сигнатуре предиката.
function move(obj: Dog | Fish) {
if (isDog(obj)) {
obj.run()
} else {
obj.swim()
}
}
#typescript
👍3🔥2
Чем полезен тип unknown?
Статья (англ.): https://michaeluloth.com/programming-types-unknown-why-useful/
Автор статьи напоминает нам, что мы не всегда можем быть уверены в типе данных, которые приходят из какого-то внешнего источника (пользовательский ввод или api). И советует использовать для таких данных тип unknown до тех пор, пока они не пройдут явную валидацию.
Такой подход позволяет избежать ряда ошибок, например, мы не сможем использовать метод .toUpperCase, пока не докажем компилятору, что это строка.
Пример не самый идеальный и в целом можно решать проблему неожиданных типов другими путями (например, использовать try-catch), но эта статья делает две хорошие вещи:
- раскрывает сущность типа unknown
- напоминает, что нельзя доверять чужим данным
#ссылки #typescript
Статья (англ.): https://michaeluloth.com/programming-types-unknown-why-useful/
Автор статьи напоминает нам, что мы не всегда можем быть уверены в типе данных, которые приходят из какого-то внешнего источника (пользовательский ввод или api). И советует использовать для таких данных тип unknown до тех пор, пока они не пройдут явную валидацию.
// не доверяем пришедшим данным
const getUserInput = (): unknown => {/*...*/}
const safe = () => {
const data = getUserInput()
if (typeof data === 'string') { // явно валидируем
data.toUpperCase() // используем метод строки
} else {
// обрабатываем некорректный тип
}
}
Такой подход позволяет избежать ряда ошибок, например, мы не сможем использовать метод .toUpperCase, пока не докажем компилятору, что это строка.
Пример не самый идеальный и в целом можно решать проблему неожиданных типов другими путями (например, использовать try-catch), но эта статья делает две хорошие вещи:
- раскрывает сущность типа unknown
- напоминает, что нельзя доверять чужим данным
#ссылки #typescript
Michael Uloth
Why Unknown Types Are Useful
Sometimes you want the type checker to help you avoid making assumptions.
👍6