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:
- získaj jednoradový kód, žiadne ďalšie funkcie
- 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.



