TypeScript filtrovať nulové hodnoty z poľa


TypeScript, režim --strictNullChecks.

Predpokladajme, že mám pole nulovateľných reťazcov (string | null)[]. Aký by bol jedno-výrazový spôsob, ako odstrániť všetky hodnoty null tak, aby výsledok mal typ string[]?

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = ???;

Array.filter tu nefunguje:

// Type '(string | null)[]' is not assignable to type 'string[]'
array.filter(x => x != null);

Poleové komprehenzie by mohli fungovať, ale TypeScript ich nepodporuje.

V skutočnosti sa dá otázka zovšeobecniť na problém filtrovania poľa ľubovoľného zjednoteného typu odstránením položiek, ktoré majú jeden konkrétny typ zo zjednotenia. Ale poďme sa zamerať na zjednotenia s null a prípadne undefined, pretože to sú najbežnejšie prípady použitia.

Odpoveď č. 1

Môžete použiť funkciu type predicate v .filter, aby ste sa vyhli vypnutiu prísneho typového overovania:

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);

Typescript 5.5+ vie odvodiť typ predikátu, takže teraz môžete jednoducho napísať toto:

const filteredArray: string[] = array.filter(x => x !== null)

Alternatívne môžete použiť array.reduce<string[]>(...).

Prísne predikáty

Hoci vyššie uvedené riešenie funguje vo väčšine scenárov, najmä keď je typ predikátu automaticky odvodený, môžete dosiahnuť prísnejšiu kontrolu typu v predikáte. Ako je uvedené, funkcia notEmpty vlastne negarantuje, že správne identifikuje, či je hodnota null alebo undefined počas kompilácie. Napríklad, skúste skrátiť jej návratový príkaz na return value !== null; a neobjaví sa žiadna chyba kompilátora, aj keď funkcia nesprávne vráti true pre undefined.

Jedným zo spôsobov, ako to zmierniť, je najprv obmedziť typ pomocou blokov riadenia toku a potom použiť fiktívnu premennú, aby mal kompilátor niečo, čo môže skontrolovať. V príklade nižšie je kompilátor schopný odvodiť, že parameter value nemôže byť null alebo undefined v čase, keď dôjde k priradeniu. Avšak, ak z podmienky if odstránite || value === undefined, uvidíte chybu kompilátora, ktorá vás upozorní na chybu v uvedenom príklade.

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
  if (value === null || value === undefined) return false;
  const testDummy: TValue = value;
  return true;
}

Slovo varovania: existujú situácie, kedy vám táto metóda stále môže zlyhať. Buďte si istí, že budete dávať pozor na problémy spojené s kontravarianciou.

Odpoveď č. 2

Ešte jeden pre istotu, keďže ľudia často zabúdajú na flatMap, ktoré dokáže zvládnuť filter a map naraz (toto tiež nevyžaduje žiadne pretypovanie na string[]):

// (string | null)[]
const arr = ["a", null, "b", "c"];
// string[]
const stringsOnly = arr.flatMap(f => f ? [f] : []);

Odpoveď č. 3

Podobne ako v odpovedi @bijou-trouvaille, stačí deklarovať <arg> je <Type> ako výstup filtračnej funkcie:

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

Odpoveď č. 4

Práve som si uvedomil, že toto môžeš urobiť:

const nonNull = array.filter((e): e is Exclude<typeof e, null> => e !== null)

Aby si ty:

  1. získaj jednoradový kód, žiadne ďalšie funkcie
  2. nemusíte vedieť typ prvkov poľa, takže toto môžete kopírovať všade!

Odpoveď č. 5

Jedna linka:

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

TypeScript ihrisko

Trik spočíva v použití typového predikátu (:s je string syntax).

Táto odpoveď ukazuje, že Array.filter vyžaduje, aby používatelia poskytli typový predikát.

Poznámka: nahraďte Boolean(s) s s !== null, ak nechcete odstraňovať nepravdivé hodnoty, ako sú prázdne reťazce "".

Odpoveď č. 6

Výsledok filter môžete previesť na typ, ktorý chcete:

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray = array.filter(x => x != null) as string[];

Toto funguje pre všeobecnejší prípad použitia, ktorý ste spomenuli, napríklad:

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[];

(kód v ihrisku)

Odpoveď č. 7

Tu je riešenie, ktoré používa NonNullable. Zistil som, že je dokonca o niečo stručnejšie ako prijatá odpoveď od @bijou-trouvaille

function notEmpty<TValue>(value: TValue): value is NonNullable<TValue> {
    return value !== null && value !== undefined;
}
const array: (string | null | undefined)[] = ['foo', 'bar', null, 'zoo', undefined];

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

Odpoveď č. 8

Môžete použiť konštrukt is na zúženie typu:

Odpoveď č. 9

Aby sa každý nemusel stále znova písať rovnaké pomocné funkcie typu guard, spojil som funkcie nazvané isPresent, isDefined a isFilled do pomocnej knižnice: https://www.npmjs.com/package/ts-is-present

Definície typov sú momentálne:

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;

Môžete to použiť takto:

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);

Keď Typescript zahrnie túto funkcionalitu, odstránim balík. Ale zatiaľ si ho užite.

Súvisiace články