Фильтрация undefined в typescript

Типизация, после того как мы с вами применили filter иногда бывает не такой однозначной как хотелось бы. Этот пост мне навеяло одно ревью.

Итак, представим, что мы продаём чашки для кофе ☕. Не обычные, а какие-нибудь многоразовые, имеющие штрихкод.

export interface CoffeeCup {
id: number;
size: 's' | 'm' | 'l';
color: string;
};

У нас есть сервис, который позволяет нам получить в результате описание нашей чашки по id, например из базы:

const getCup = (id: number): CoffeeCup | undefined => {
// ...
return;
}

В результате нам может вернуться или чашка кофе или undefined, так как такой чашки у нас более нет на складе.

Далее в одной из функций мы хотим проверить по id чашки и получить только тем, которые у нас действительно есть в наличии. Ну и конечно мы любим писать код в функциональном стиле, поэтому пишем:

const inStock = [1, 2, 3];
.map(s => getCup(s))
.filter(s => s) // (CoffeeCup | undefined)[]

Казалось бы с точки зрения логики всё хорошо, но с точки зрения типов нет. Мы получаем CoffeeCup, или undefined, несмотря на то, что мы всё отфильтровали.

Когда мы принимаем map, мы из обычных number получаем (CoffeeCup | undefined)[], затем применяя фильтр мы ничего не меняем с точки зрения типов. Для TypeScript удаление каждого элемента типа undefined, почему-то не приводит к сужению типов. Нам нужно ему помочь.

Решение проблемы — 1

Мы можем явно сделать type assertion к нужному нам типу:

const inStock = [1, 2, 3]
.map(s => getCup(s))
.filter(s => s) as CoffeeCup[] // CoffeeCup []

Тогда мы получим верный тип, но реализация фильтра остаётся на нашей совести, что не очень безопасно с точки зрения типизации. Мы фактически говорим TypeScript, чтобы он игнорировал свои вывод и использовал то, что мы ему дали.

Решение проблемы — 2

Вместо явного указания, мы можем сделать свой type guard. Они по сути позволяют сузить типы за счёт дополнительных проверок:

const inStock = [1, 2, 3]
.map(s => getCup(s))
.filter((s): s is CoffeeCup => !!s) // CoffeeCup []

Обратите внимание на функцию внутри filter.

(s): s is CoffeeCup => !!s

По сути это type guard, который проверяет что объект является типом CoffeeCup. В результате мы получаем отфильтрованный объект, где TypeScript будет точно уверен, что его тип не будет undefined.

--

--

--

IT Director in car selling company. In love with TypeScript, NodeJS and Angular.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Anton Larichev

Anton Larichev

IT Director in car selling company. In love with TypeScript, NodeJS and Angular.

More from Medium

Typescript dynamic type with any

Features of Different Javascript Framework : Node Js, BackBone JS, Ext Js

SPAs: from Server to Client and back

Typescript for Noobies