TypeScript从数组中过滤掉null值

347

TypeScript, --strictNullChecks 模式。

假设我有一个可为空的字符串数组 (string | null)[]。有什么单表达式的方法可以以这样一种方式删除所有 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 的联合类型,因为这些是最常见的用例。


遍历数组时,检查索引是否为空,如果不为空,则将它们添加到过滤后的数组中,这有什么问题吗? - Markus G.
3
现在我所做的是迭代+条件判断+插入。我觉得这样太冗长了。 - SergeyS
在 playground 中,使用您发布的 array.filter 的方式非常好。甚至不需要 : string[],只需这样:const filterdArray = array.filter(x => x != null); 编译器会推断出 filterdArray 的类型为 string[]。您使用的 TypeScript 版本是什么? - Nitzan Tomer
6
在游乐场中选择“选项”,并勾选“strictNullChecks”。 - SergeyS
22个回答

3

简单使用

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


1
由于某些不幸的原因,它在TS中不会声明类型。 - Dmitri Pisarev
1
@DmitriPisarev 如果你想使用类型推断,可以使用类似以下代码: type Truthy = T extends false | '' | 0 | null | undefined ? never : T; function truthy(value: T): value is Truthy { return Boolean(value); } const truthyArr = arr.filter(truthy); - shrijan tripathi

2
我认为这将是一种简单的方法,代码更简洁。
const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(a => !!a);

1
这个解决方案不是类型安全的 - 在打开strictNullChecks后无法编译。 - Jan Aagaard
1
请注意,空字符串 '' 被认为是假值,并且在过滤期间被删除。 - Roman Vottner

2

如果您可以接受另一个.map()的开销,一种优雅的解决方案是使用非空断言运算符

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

如果您想保留未定义的变量,可以在变量上使用typeof,并使用实用类型Exclude从类型中删除null。

const array = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = array
  .filter(s => s !== null)
  .map(s => s as Exclude<typeof s, null>);

1

我多次回到这个问题,希望一些新的TypeScript特性或类型能够修复它。

这里有一个我很喜欢的简单技巧,用于将map与后续的filter结合使用。

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能够提供更好的解决方案。

1

最短的方法:

const validData = array.filter(Boolean)

3
不错的技巧,但可能并不完全符合要求。如果数组包含空字符串,则过滤器会将其删除。这可能会产生意想不到的后果。最初的请求是要删除空值。我建议编辑响应以指定边角情况。 - filippo

1

我相信你的一切都很好,除了类型检查只是使过滤的类型与返回类型没有区别。

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

你说得对,暂时类型的“Opt-out”会起作用。还有更严格的解决方案吗? - SergeyS
那是我的第一反应 - 但 TypeScript 不允许这样做。然而,由于 filteredArray 被定义为 string[] 类型,它在我看来已经足够严格了。 - Digvijay

1

使用 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 Playground 这里


这是一个权宜之计,但在我看来并不是一个好的解决方案:使用reduce而不是filter让人感到“惊讶”,而且效率也较低。 - vcarel

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

0

如果您正在使用过滤器检查空值和其他条件,那么可以简单地使用此方法。希望这对于正在寻找对象数组解决方案的人有所帮助。

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

0

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

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

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

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

比起在新数组上手动强制转换为string[],这种方法更长但更安全。

步骤如下:

  1. 从原始数组中获取类型:
typeof arrayWithNulls[number] // => string | null

排除null值:
NonNullable<typeof arrayWithNulls[number]> // => string

将其变成数组:
NonNullable<typeof arrayWithNulls[number]>[] // => string[]

链接:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接