TypeScript, - режим strictNullChecks.

Предположим, у меня есть массив строк, допускающих значение NULL (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.

Фактически вопрос может быть обобщен на проблему фильтрации массива любого типа объединения путем удаления из объединения записей, имеющих один конкретный тип. Но давайте сосредоточимся на союзах с null и, возможно, undefined, поскольку это наиболее распространенные варианты использования.

SergeyS

Ответов: 16

Ответы (16)

Вы можете использовать предикат типа в .filter, чтобы избежать отказа от строгой проверки типов:

функция notEmpty  (значение: TValue | null | undefined): значение равно TValue {
    возвращаемое значение! == null && value! == undefined;
}

массив констант: (строка | null) [] = ['foo', 'bar', null, 'zoo', null];
const filterArray: string [] = array.filter (notEmpty);

В качестве альтернативы вы можете использовать array.reduce (...).

Обновление 2021: более строгие предикаты

Хотя это решение работает в большинстве сценариев, вы можете получить более строгую проверку типа в предикате. Как представлено, функция notEmpty на самом деле не гарантирует, что она правильно определяет, является ли значение null или undefined во время компиляции. Например, попробуйте сократить его оператор возврата до возвращаемое значение! == null;, и вы не увидите ошибки компилятора, даже если функция будет неправильно возвращать true на undefined.

Один из способов смягчить это - ограничить тип сначала с помощью блоков потока управления, а затем использовать фиктивную переменную, чтобы дать компилятору что-то проверить. В приведенном ниже примере компилятор может сделать вывод, что параметр value не может быть null или undefined к тому времени, когда он попадет в назначение. Однако, если вы удалите || value === undefined из условия if вы увидите ошибку компилятора, информирующую вас об ошибке в примере выше.

функция notEmpty  (значение: TValue | null | undefined): значение равно TValue {
  если (значение === null || значение === undefined) return false;
  const testDummy: TValue = значение;
  вернуть истину;
}

Предупреждение: существуют ситуации, когда этот метод все еще может вас подвести. Обязательно помните о проблемах, связанных с контравариантностью.

Вы можете преобразовать результат 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[];

( код на игровой площадке)

Чтобы всем не приходилось писать вспомогательные функции одного и того же типа снова и снова, я объединил функции с именами isPresent, isDefined и isFilled во вспомогательную библиотеку. : https://www.npmjs.com/package/ts-is-present

Определения типов в настоящее время:

export declare function isPresent(t: T | undefined | null): t is T;
export declare function isDefined(t: T | undefined): t is T;
export declare function isFilled(t: T | null): t is T;

Вы можете использовать это так:

импорт {isDefined} из 'ts-is-present';

type TestData = {
  данные: строка;
};

const результаты: Array  = [
  {data: 'hello'},
  неопределенный,
  {данные: 'мир'}
];

const definedResults: Array  = results.filter (isDefined);

console.log (definedResults);

Когда Typescript объединит эту функциональность в, я удалю этот пакет. А пока наслаждайтесь.

Если вы уже используете Lodash, вы можете использовать compact. Или, если вы предпочитаете Ramda, у ramda-адъюнкта также есть функция 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(array: List | null | undefined): T[];

Один лайнер:

const filteredArray: string[] = array.filter((s): s is string => Boolean(s));

Игровая площадка TypeScript

Уловка состоит в том, чтобы передать предикат типа (: s - это строка синтаксис).

Этот ответ показывает, что Array.filter требует от пользователей предоставления предиката типа.

TypeScript имеет несколько утилит для определения типа массива и исключения из него значений null:

const arrayWithNulls = ["foo", "bar", null, "zoo", null]

type ArrayWithoutNulls = NonNullable  []

const arrayWithoutNulls = arrayWithNulls.filter (x => x! = null) как ArrayWithoutNulls

Дольше, но безопаснее, чем просто вручную преобразовать как строку [] в ваш новый массив.

Пошагово:

  1. Получить типы из исходного массива:
typeof arrayWithNulls [число] // => строка | нулевой
  1. Исключить значения null:
NonNullable  // => строка
  1. Сделать массив:
NonNullable  [] // => строка []

Ссылки:

  • NonNullable (официальный документ)
  • typeof array [number] (сообщение в блоге, я ничего не нашел об этом в официальном документе)
const filterdArray = array.filter(f => !!f) as string[];

Вот решение, которое я считаю даже немного более кратким, чем принятый ответ @ bijou-Trouvaille

const notEmpty = (value: T): value is NonNullable => !!value 
const array: (string | null | undefined)[] = ['foo', 'bar', null, 'zoo', undefined];

const filteredArray: string[] = array.filter(notEmpty);
console.log(filteredArray)
[LOG]: ["foo", "bar", "zoo"]

Если вы проверяете null с другими условиями, используя просто фильтр, это можно использовать, надеюсь, это поможет тем, кто ищет решения для массива объектов

array.filter(x => x != null);
array.filter(x => (x != null) && (x.name == 'Tom'));

Думаю, это будет простой подход с более чистым кодом

const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(a => !!a);

Я считаю, что у вас все хорошо, за исключением того, что проверка типа просто делает фильтруемый тип не отличным от возвращаемого типа.

массив констант: (string | null) [] = ["foo", "bar", null, "zoo", null];
const filterdArray: string [] = array.filter (f => f! == undefined && f! == null) как любое;
console.log (filterdArray);

Еще одна хорошая мера, так как люди часто забывают о flatMap, который может обрабатывать filter и map за один раз (это также не требует кастинга в строка []):

// (строка | ноль) []
const arr = ["а", ноль, "б", "с"];
// нить[]
const stringsOnly = arr.flatMap (f => f? [f]: []);

Вы также можете использовать двойное отрицание (!!), чтобы отфильтровать все ложные значения:

  • ноль
  • не определено
  • Пустая строка "" ('')
  • Число 0
  • Число NaN например,
 массив? .Filter (item => !! item) .map ((item: any)

просто используйте

array.filter (логический);

Это будет работать для всех значений истинности.

Это, к сожалению, не обеспечивает вывод типа, нашел это решение на здесь


type Truthy  = T extends false | '' | 0 | null | неопределенный ? никогда: T; // из lodash

function truthy  (value: T): value is Truthy  {
    return Boolean (значение); // или !! значение
}

const arr = ["привет", "felow", "разработчик", "", null, undefined];

const truthyArr = arr.filter (правда);

// типом truthyArr будет строка []

Подобно ответу @ bijou-Trouvaille, вам просто нужно объявить is в качестве выходных данных функции фильтра:

array.filter((x): x is MyType => x !== null);

Используя уменьшить

В некоторых ответах предлагается уменьшить, вот как:

const languages ​​= ["fr", "en", undefined, null, "", "de"]

// тот, который мне больше нравится:
languages.reduce  ((предыдущий, текущий) => текущий? [... предыдущий, текущий]: предыдущий, [])

// или
languages.reduce ((предыдущий, текущий) => текущий? [... предыдущий, текущий]: предыдущий, массив <строка> ())

// или
const reducer = (предыдущая: строка [], текущая: строка | undefined | null) => текущая? [... предыдущий, текущий]: предыдущий
languages.reduce (редуктор, [])

Результат: ["fr", "en", "de"]

TS Playground здесь.

2022 WebDevInsider