TypeScript отфильтровывает нули из массива
TypeScript, режим --strictNullChecks.
Предположим, у меня есть массив обнуляемых строк (string | null)[]. Что может быть способом с одним выражением для удаления всех нулей таким образом, чтобы у результата был тип string []?
const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = ???;
Array.filter не работает здесь:
// Type '(string | null)[]' is not assignable to type 'string[]'
array.filter(x => x != null);
Могут работать массивы, но они не поддерживаются TypeScript.
На самом деле вопрос может быть обобщен на проблему фильтрации массива любого типа объединения путем удаления записей, имеющих один конкретный тип, из объединения. Но давайте сосредоточимся на союзах с нулевым и, возможно, неопределенным, так как это наиболее распространенные варианты использования.
24 ответа
Вы можете использовать функцию предиката типа в .filter
чтобы избежать отказа от строгой проверки типов:
function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}
const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(notEmpty);
В качестве альтернативы вы можете использовать array.reduce<string[]>(...)
,
Еще одна надежная мера, о которой часто забывают flatMap
который может справиться filter
а также map
за один раз (это также не требует преобразования в string[]
):
// (string | null)[]
const arr = ["a", null, "b", "c"];
// string[]
const stringsOnly = arr.flatMap(f => typeof f === "string" ? [f] : []);
Подобно ответу @bijou-trouvaille, вам просто нужно объявить <arg> is <Type>
в качестве вывода функции фильтра:
array.filter((x): x is MyType => x !== null);
Только что понял, что вы можете сделать это:
const nonNull = array.filter((e): e is Exclude<typeof e, null> => e !== null)
Чтобы вы:
- получить однострочный, без дополнительных функций
- не обязательно знать тип элементов массива, так что копировать можно везде!
Один лайнер:
const filteredArray: string[] = array.filter((s): s is string => Boolean(s));
Уловка состоит в том, чтобы передать предикат типа (
:s is string
синтаксис).
Этот ответ показывает, что
Array.filter
требует от пользователей предоставления предиката типа.
Вы можете бросить свой filter
результат в тип, который вы хотите:
const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray = array.filter(x => x != null) as string[];
Это работает для более общего случая использования, который вы упомянули, например:
const array2: (string | number)[] = ["str1", 1, "str2", 2];
const onlyStrings = array2.filter(x => typeof x === "string") as string[];
const onlyNumbers = array2.filter(x => typeof x === "number") as number[];
Вот решение, которое я считаю даже немного более кратким, чем принятый ответ от @ bijou-Trouvaille
const notEmpty = <T>(value: T): value is NonNullable<typeof value> => !!value
const array: (string | null | undefined)[] = ['foo', 'bar', null, 'zoo', undefined];
const filteredArray: string[] = array.filter(notEmpty);
console.log(filteredArray)
[LOG]: ["foo", "bar", "zoo"]
Чтобы всем не приходилось писать вспомогательные функции одного и того же типа снова и снова, я объединил функции, называемые isPresent
, isDefined
а также isFilled
во вспомогательную библиотеку: https://www.npmjs.com/package/ts-is-present
Определения типов в настоящее время:
export declare function isPresent<T>(t: T | undefined | null): t is T;
export declare function isDefined<T>(t: T | undefined): t is T;
export declare function isFilled<T>(t: T | null): t is T;
Вы можете использовать это так:
import { isDefined } from 'ts-is-present';
type TestData = {
data: string;
};
const results: Array<TestData | undefined> = [
{ data: 'hello' },
undefined,
{ data: 'world' }
];
const definedResults: Array<TestData> = results.filter(isDefined);
console.log(definedResults);
Когда Typescript объединит эту функциональность в, я удалю пакет. Но пока наслаждайтесь.
Если вы уже используете Lodash, вы можете использовать. Или, если вы предпочитаете Рамду, добавка Рамда также имеет
compact
функция.
У обоих есть типы, поэтому ваш tsc будет счастлив и в результате получит правильные типы.
Из файла Lodash d.ts:
/**
* Creates an array with all falsey values removed. The values false, null, 0, "", undefined, and NaN are
* falsey.
*
* @param array The array to compact.
* @return Returns the new array of filtered values.
*/
compact<T>(array: List<T | null | undefined | false | "" | 0> | null | undefined): T[];
просто используйте
array.filter(Boolean);
Это будет работать для всех истинных ценностей.
Это, к сожалению, не обеспечивает вывод типа, нашел это решение здесь
type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T; //from lodash
function truthy<T>(value: T): value is Truthy<T> {
return Boolean(value); // or !!value
}
const arr =["hello","felow","developer","",null,undefined];
const truthyArr = arr.filter(truthy);
// the type of truthyArr will be string[]
Я считаю, что у вас все это хорошо, за исключением того, что проверка типов просто делает отфильтрованный тип не отличным от возвращаемого типа.
const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array.filter(f => f !== undefined && f !== null) as any;
console.log(filterdArray);
Если вы можете принять накладные расходы другого.map()
элегантное решение — использовать ненулевой оператор утверждения .
const array = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array.filter(s => s != null).map(s => s!);
Если вы хотите сохранить undefines, вы можете использоватьtypeof
по переменной и типу утилитыExclude
чтобы удалить нули из типа.
const array = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array
.filter(s => s !== null)
.map(s => s as Exclude<typeof s, null>);
Думаю, это будет простой подход с более чистым кодом.
const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(a => !!a);
С использованием
Некоторые ответы предполагают
reduce
вот как:
const languages = ["fr", "en", undefined, null, "", "de"]
// the one I prefer:
languages.reduce<string[]>((previous, current) => current ? [...previous, current] : previous, [])
// or
languages.reduce((previous, current) => current ? [...previous, current] : previous, Array<string>())
// or
const reducer = (previous: string[], current: string | undefined | null) => current ? [...previous, current] : previous
languages.reduce(reducer, [])
Результат:
["fr", "en", "de"]
ТС Детская площадка здесь .
Я много раз возвращался к этому вопросу, надеясь, что какая-то новая функция Typescript или набор текста могут это исправить.
Вот простой трюк, который мне очень нравится при комбинировании карты с последующим фильтром.
const animals = ['cat', 'dog', 'mouse', 'sheep'];
const notDogAnimals = animals.map(a =>
{
if (a == 'dog')
{
return null!; // just skip dog
}
else {
return { animal: a };
}
}).filter(a => a);
Вы увидите, что я возвращаюсь
null!
который на самом деле становится типом
never
- это означает, что окончательный тип не имеет значения null.
Это небольшая вариация исходного вопроса, но я довольно часто попадаю в этот сценарий, и это помогает избежать другого вызова метода. Надеюсь, когда-нибудь Typescript придумает лучший способ.
В TypeScript есть несколько утилит для определения типа массива и исключения из него значений:
const arrayWithNulls = ["foo", "bar", null, "zoo", null]
type ArrayWithoutNulls = NonNullable<typeof arrayWithNulls[number]>[]
const arrayWithoutNulls = arrayWithNulls.filter(x => x != null) as ArrayWithoutNulls
Дольше, но безопаснее, чем просто заливка вручную
as string[]
в вашем новом массиве.
Шаг за шагом:
- Получим типы из исходного массива:
typeof arrayWithNulls[number] // => string | null
- Исключить
null
значения:
NonNullable<typeof arrayWithNulls[number]> // => string
- Сделайте это массивом:
NonNullable<typeof arrayWithNulls[number]>[] // => string[]
Ссылки:
NonNullable
(официальный документ)typeof array[number]
(сообщение в блоге, я ничего не нашел об этом в официальном документе)
Если вы проверяете null с другими условиями, используя просто фильтр, это можно использовать, надеюсь, это поможет тем, кто ищет решения для object array
array.filter(x => x != null);
array.filter(x => (x != null) && (x.name == 'Tom'));
Объединив один из моих любимых ответов выше, с некоторыми общими приемами и расширением интерфейса Array, я смог сделать глобальное определение, которое после добавления в ваш модуль позволяет «сплющить» любой массив, удалив все нулевые значения, заменив(any|undefined|null)[]
сany[]
.
Вот так:mixedArray.squish()
хорошо для цепочки и карты.
Просто добавьте этот код где-нибудь в свой модуль (не стесняйтесь пропустить материал eslint, но мой набор беспокоил меня в нескольких вещах здесь):
/* eslint-disable no-unused-vars */
/* eslint-disable no-extend-native */
declare global {
interface Array<T> {
squish<NonNull, Nullable extends (NonNull | undefined | null)>(): NonNull[];
}
}
if (!Array.prototype.squish) {
Array.prototype.squish = function squish<NonNull, T extends(NonNull|undefined|null)>
(this: T[]): NonNull[] {
return this.flatMap((e) => (e ? [e] : [])) as NonNull[]
}
}
Или вы можете попробовать пакет: @p4ck93/ts-is
https://www.npmjs.com/package/@p4ck493/ts-is
В примере используется метод CDN, но пакет также поддерживает машинописный текст.
Вы также можете использовать двойное отрицание (!!), чтобы отфильтровать все ложные значения:
- значение NULL
- неопределенный
- Пустая строка "" ('')
- Число 0
- Число NaN например,
array?.filter(item => !!item).map((item: any)
const arr1 = [.....]
const arr2 = [.....]
const arr3 = [.....]
const newArr = [
...(arr1 || []),
...(arr2 || []),
...(arr3 || []),
]