Как отфильтровать null и undefined из массива в TypeScript: полное руководство
Проблема: TypeScript не выводит правильный тип после filter()
Вы столкнулись с классической проблемой TypeScript: метод filter() не умеет автоматически сужать типы на основе предиката. Даже если вы явно проверяете на null и undefined, TypeScript по умолчанию сохраняет исходный тип массива. Это связано с тем, что сигнатура filter() в стандартной библиотеке не использует пользовательскую защиту типов (type guard).
Решение 1: Type predicate (пользовательская защита типов) — ЛУЧШИЙ ПОДХОД
Создайте функцию-предикат с возвращаемым типом item is T. Это явно сообщает TypeScript, что после фильтрации тип элемента будет T (без null и undefined).
function isNotNullOrUndefined<T>(item: T | null | undefined): item is T {
return item !== null && item !== undefined;
}
// Использование:
const mixedArray: (number | null | undefined)[] = [1, null, 2, undefined, 3, null, 4];
const filteredArray = mixedArray.filter(isNotNullOrUndefined);
// ✅ filteredArray имеет тип number[]
Как это работает:
item is T— это type predicate (предикат типа). Он говорит TypeScript: "Если функция возвращает true, то item гарантированно имеет тип T".- Функция универсальна (generic) — работает с любыми типами:
string | null,MyInterface | undefinedи т.д. - TypeScript автоматически выводит тип
number[]дляfilteredArray.
Решение 2: Встроенный type guard через arrow function
Можно использовать стрелочную функцию прямо внутри filter() с явным указанием type predicate:
const filteredArray = mixedArray.filter(
(item): item is number => item !== null && item !== undefined
);
// ✅ filteredArray: number[]
Важно: Type predicate (item is number) указывается после аргументов, перед телом функции. Без этого TypeScript не сможет сузить тип.
Решение 3: Использование вспомогательных утилит из библиотек
Если вы используете популярные библиотеки утилит:
// С использованием lodash
import { compact } from 'lodash';
const filteredArray = compact(mixedArray); // number[]
// С использованием ramda
import { reject, isNil } from 'ramda';
const filteredArray = reject(isNil, mixedArray); // number[]
Решение 4: Собственная обёртка (если нужно часто)
Создайте кастомный метод на прототипе Array или утилитарную функцию:
// Вариант с глобальным расширением Array
declare global {
interface Array<T> {
filterNonNull<U>(this: (U | null | undefined)[]): U[];
}
}
Array.prototype.filterNonNull = function<T, U>(this: (U | null | undefined)[]): U[] {
return this.filter((item): item is U => item != null);
};
// Использование:
const filteredArray = mixedArray.filterNonNull(); // number[]
Что выбрать?
- Для одноразового использования — подойдёт стрелочная функция с type predicate (Решение 2).
- Для повторяющегося кода — создайте универсальную функцию
isNotNullOrUndefined(Решение 1). - Если используете библиотеки — проверьте, может быть там уже есть готовое решение (lodash compact).
Важное замечание: Метод filter(Boolean) отфильтрует не только null/undefined, но и 0, "", false — используйте его только если это поведение вам подходит.
Альтернативный подход: Использование flatMap() или reduce()
Если вы не хотите использовать type predicate, можно применить flatMap() или reduce() для фильтрации с правильным выводом типов.
Решение через flatMap():
const filteredArray = mixedArray.flatMap(item =>
item !== null && item !== undefined ? [item] : []
);
// ✅ filteredArray: number[]
flatMap() возвращает массив, и TypeScript корректно выводит тип на основе возвращаемого значения. Если элемент валидный — возвращаем массив с ним, если нет — пустой массив.
Решение через reduce():
const filteredArray = mixedArray.reduce<number[]>((acc, item) => {
if (item !== null && item !== undefined) {
acc.push(item);
}
return acc;
}, []);
// ✅ filteredArray: number[]
Здесь мы явно указываем тип аккумулятора number[] через generic reduce<number[]>.
Когда это полезно?
- Если вы не хотите создавать отдельные функции-предикаты.
- Если вам нужно одновременно фильтровать и трансформировать данные.
- В проектах, где запрещены пользовательские type guard (редко, но бывает).
Недостатки:
- Код менее читаемый, чем с type predicate.
flatMap()может быть медленнее на больших массивах (создаёт промежуточные массивы).reduce()более многословен.
Рекомендую использовать основной подход с type predicate — он наиболее идиоматичен для TypeScript и легко читается.
Ответов пока нет.
Войдите чтобы оставить ответ