К списку постов

Фильтрация 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.

Подпишись на статьи

Всего 1 раз в месяц я буду высылать вам подборки свежих статей с сайта.

Если хотите получать материалы и статьи каждую неделю подпишитесь на канал в Telegram PurpleCode.