React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
TypeScript: Защитники типов (Type Guards)

Когда мы работаем с объединениями (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 до тех пор, пока они не пройдут явную валидацию.


// не доверяем пришедшим данным
const getUserInput = (): unknown => {/*...*/}

const safe = () => {
const data = getUserInput()

if (typeof data === 'string') { // явно валидируем
data.toUpperCase() // используем метод строки
} else {
// обрабатываем некорректный тип
}
}


Такой подход позволяет избежать ряда ошибок, например, мы не сможем использовать метод .toUpperCase, пока не докажем компилятору, что это строка.

Пример не самый идеальный и в целом можно решать проблему неожиданных типов другими путями (например, использовать try-catch), но эта статья делает две хорошие вещи:

- раскрывает сущность типа unknown
- напоминает, что нельзя доверять чужим данным

#ссылки #typescript
👍6