TypeScript 从数组中筛选出空值

字体,--strictNullChecks模式。

假设我有一个可为空的字符串数组 (string | null)[]。用什么样的 单一表达式方法去除所有空值,使得结果具有 string[]类型?

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

Filter 在这里不起作用:

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

数组理解可以工作,但 TypeScript 不支持它们。

实际上,这个问题可以推广到过滤任何联合类型的数组的问题,方法是从联合中删除具有特定类型的条目。但是让我们把注意力集中在具有 null 或者可能未定义的联合上,因为这些是最常见的用例。

124206 次浏览

我相信除了类型检查只是让过滤后的类型与返回类型不不同之外,您都做得很好。

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array.filter(f => f !== undefined && f !== null) as any;
console.log(filterdArray);

你可以将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[];

(操场上的代码)

你可以在.filter中使用< < em >类型谓词/ em >函数来避免选择退出严格的类型检查:

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[]>(...)

2021年更新:更严格的谓词

虽然这种解决方案适用于大多数场景,但您可以在谓词中进行更严格的类型检查。如上所述,函数notEmpty实际上并不保证它在编译时正确识别值是null还是undefined。例如,尝试将其返回语句缩短为return value !== null;,你将不会看到编译器错误,即使该函数将在undefined上错误地返回true

缓解这种情况的一种方法是首先使用控制流块约束类型,然后使用一个虚拟变量让编译器进行检查。在下面的例子中,编译器在赋值时能够推断出value形参不能是nullundefined。然而,如果你从if条件中删除|| value === undefined,你会看到一个编译器错误,通知你上面例子中的错误。

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

提醒一句:在某些情况下,这种方法仍然会失败。一定要注意与抗变性相关的问题。

类似于@bijou-trouvaille的回答,你只需要声明<arg> is <Type>作为过滤器函数的输出:

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

我认为这将是一个简单的方法,有更清晰的代码

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

为了避免每个人都不得不一遍又一遍地编写相同类型的保护helper函数,我将名为isPresentisDefinedisFilled的函数绑定到一个helper库: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捆绑这个功能时,我将删除这个包。但是,现在,享受吧。

因为人们经常忘记flatMap,它可以一次性处理filtermap(这也不需要任何转换为string[]):

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

如果你正在使用过滤器检查null和其他条件,这可以简单地使用,希望这有助于寻找object array的解决方案的人

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

简单地使用

array.filter(Boolean);


这对所有真值都适用。

这个,不幸的是,不提供类型推断,找到了这个解决方案 在这里 < / p >


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


如果你已经使用了Lodash,你可以使用compact。 或者,如果你更喜欢Ramda, Ramda -adjunct也有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[];

下面是一个使用NonNullable的解决方案。我发现它甚至比@ bijouu -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"]

TypeScript有一些实用工具来推断数组的类型,并从数组中排除null值:

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


type ArrayWithoutNulls = NonNullable<typeof arrayWithNulls[number]>[]


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

比手动在新数组上强制使用as string[]更长,但更安全。

循序渐进:

  1. 从原始数组中获取类型:
typeof arrayWithNulls[number] // => string | null
  1. 排除null值:
NonNullable<typeof arrayWithNulls[number]> // => string
  1. 让它成为一个数组:
NonNullable<typeof arrayWithNulls[number]>[] // => string[]

链接:

一个衬套:

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

TypeScript playground

诀窍是传递一个类型谓词 (:s is string语法)。

这个答案表明Array.filter要求用户提供一个类型谓词。

使用reduce

一些答案建议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"]

TS游乐场在这里

const filterdArray = array.filter(f => !!f) as string[];

我已经多次回到这个问题上,希望一些新的Typescript特性或类型可以解决这个问题。

这里有一个简单的技巧,我很喜欢结合map和后续过滤器。

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会提出一个更好的方法。

最短的方法:

const validData = array.filter(Boolean)

刚刚意识到你可以这样做:

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

这样你:

  1. 得到一行代码,没有附加函数
  2. 不需要知道数组元素的类型,所以你可以到处复制!

或者您可以尝试包:@p4ck93/ts-is

https://www.npmjs.com/package/@p4ck493/ts-is

示例使用CDN方法,但是包也支持typescript。

<script>var exports = {};</script>
<script src="//unpkg.com/@p4ck493/ts-is@3.0.1/dist/index.js"></script>
<script>
const {is} = exports;
console.log('is.string: ', is.string('')); // true
console.log('is.string.empty: ', is.string.empty('')); // true
console.log('is.string.not.empty: ', is.string.not.empty('')); // false
    

    

const array = ["foo", "bar", null, "zoo", null];
const filterdArray = array.filter(is.string.not.empty);
   

console.log('array:', array);
console.log('filterdArray:', filterdArray);
</script>