告诉TypeScript编译器的方法Array.prototype.filter从数组中删除某些类型?

shu*_*ksh 13 typescript

我试图通过使用Array.prototype.filter从数组中过滤null(undefined)元素,但TypeScript编译器似乎无法识别"过滤器"函数的派生数组并且未能通过类型检查.

假设遵循简化代码,其中我有一个带有(number | undefined)[]类型的数组,并希望过滤undefined以适合number []数组.

const arry = [1, 2, 3, 4, "5", 6];
const numArry: number[] = arry
    .map((i) => {
        return typeof i === "number" ? i : void 0;
    })
    .filter((i) => i);
Run Code Online (Sandbox Code Playgroud)

错误说:

类型'(number | undefined)[]'不能分配给'number []'.输入'号码| undefined'不能分配给'number'类型.类型'undefined'不能分配给'number'类型.

我可以将结果数组转换为数字[],如下所示,知道过滤器函数删除undefined.

const arry = [1, 2, 3, 4, "5", 6];
const numArry: number[] = (arry
    .map((i) => {
        return typeof i === "number" ? i : void 0;
    })
    .filter((i) => i) as Number[]);
Run Code Online (Sandbox Code Playgroud)

除了铸造之外,还有更好的方法来实现这一目标吗?

环境:TSC2.1启用了strictNullChecks.

小智 59

使用 TypeScript 的 User-Defined Type Guards 功能:

const arry = [1, 2, 3, 4, "5", 6];
const numArry: number[] = arry
    .filter((i): i is number => {
        return typeof i === "number";
    });
// numArry = [1, 2, 3, 4, 6]
Run Code Online (Sandbox Code Playgroud)

i is number在回调函数中查看。这个技巧使我们能够投射 Array.filter 结果的类型。

  • 寻找社区的评论... [这个 TS bug](https://github.com/microsoft/TypeScript/issues/7657) 表明这个问题已经被修复,但在我看来,这个类型防护闻起来像黑客。是什么赋予了? (3认同)
  • 这是强制施法,而且很危险。无论如何,类型的逻辑检查都没有经过验证,因此它可能是错误的,即使我们转换的类型发生变化,也不会出错。 (3认同)
  • 静态分析有其局限性。如果声明和类型检查之间有代码,则在不运行代码的情况下很难知道运行时数组中的内容。 (2认同)

Ema*_*röm 25

flatMap以Typescript 理解的类型安全方式从数组中过滤值的最简单方法是使用map返回一个空数组来删除您想要删除的任何内容。

例子:

const myArray = [1,2,3,4,"5",6,7, undefined,9, 0]
const filteredArray = arr.flatMap(val => typeof val === "number" ? val : [])
// filteredArray: [ 1, 2, 3, 4, 6, 7, 9, 0 ]
Run Code Online (Sandbox Code Playgroud)

flatMap分两步进行。首先,它映射数组并执行您给它的任何函数。然后它会展平其中的所有数组。

  1. 映射数组并得到: [ 1, 2, 3, 4, [], 6, 7, [], 9, 0 ]

  2. 展平数组中的所有数组: [ 1, 2, 3, 4, 6, 7, 9, 0 ]

瞧!现在你有了一个类型化数组并且 Typescript 很高兴。

在此输入图像描述


Kar*_*ski 15

创建一个类型保护

function isDefined<T>(argument: T | undefined): argument is T {
    return argument !== undefined
}
Run Code Online (Sandbox Code Playgroud)

使用它作为您的类型谓词:

const foo: number[] = [1, 2, undefined, 4].filter(isDefined)
Run Code Online (Sandbox Code Playgroud)

说明

Array.prototype.filter有一些重载。其中之一了解返回值将取决于您的谓词函数。它使用类型保护:

filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
Run Code Online (Sandbox Code Playgroud)

使用适当的类型保护(而不是使用快捷方式并依靠隐式强制)可以帮助TypeScript选择此特定的重载。


Diu*_*lei 7

map(...)函数签名是:

map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
Run Code Online (Sandbox Code Playgroud)

在您的情况下,泛型类型U将是:number | undefined

filter(...)签名是:

filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[];        
Run Code Online (Sandbox Code Playgroud)

由于T来自数组接口签名 ( Array<T>),因此返回类型将是与value参数类型相同的数组(泛型类型T)。在你的情况下number | undefined

这就是为什么您的返回类型是number | undefined.

基于此,您将需要使用强制转换表达式。如果您不想要这种行为,您可以删除该--strictNullChecks标志。


art*_*tem 6

内置filter函数不可能,它被声明返回一个与它接收的类型完全相同的数组.

但是,可以定义自己的完全类型安全的过滤器函数,该函数接受数组和用户定义的类型保护函数,并返回不同类型的数组.

不确定有用,但这里是:

function typeFilter<T, R extends T>(a: T[], f: (e: T) => e is R): R[] {
    const r: R[] = [];
    a.forEach(e => { if (f(e)) r.push(e) });
    return r;
}
Run Code Online (Sandbox Code Playgroud)

它可以像这样使用:

const arry = [1, 2, 3, 4, "5", 6];

function isNumber(e): e is number {
    return typeof e === 'number';
}

const numArry: number[] = typeFilter(arry, isNumber);
Run Code Online (Sandbox Code Playgroud)

不幸的是,isNumber()必须将其定义为单独的显式类型函数,因为编译器不够聪明,无法识别内联函数 e => typeof e === 'number'也是类型保护.