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

Posted

技术标签:

【中文标题】告诉 TypeScript 编译器 Array.prototype.filter 从数组中删除某些类型的方法?【英文标题】:Way to tell TypeScript compiler Array.prototype.filter removes certain types from an array? 【发布时间】:2017-08-18 01:19:27 【问题描述】:

我正在尝试使用 Array.prototype.filter 过滤数组中的空(未定义)元素,但 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);

错误提示:

类型 '(number | undefined)[]' 不能分配给类型 'number[]'。 输入'号码 | undefined' 不可分配给类型 'number'。 类型“未定义”不能分配给类型“数字”。

我可以将结果数组转换为 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) as Number[]);

除了强制转换之外,还有更好的方法来实现这一点吗?

环境:启用 strictNullChecks 的 TSC2.1。

【问题讨论】:

您使用的是什么版本的打字稿?您的初始代码对我来说很好,无需修改 TS 2.0.9 这个问题假设 --strictNullChecks 这个问题部分与how type guards aren't currently propagated on filters over Arrays, but instead only ReadonlyArrays有关。 谢谢@artem 是对的。忘了提及,但使用启用了 strictNullChecks 的 tsc 2.1。 【参考方案1】:

解决方案

const arry = [1, 2, 3, 4, "5", 6];
const numArry = arry.reduce((acc, x) => typeof x === 'number' ? [...acc, x] : acc, [] as number[]);

我反对使用类型保护,因为您实际上可以编写任何逻辑并且它仍然可以工作。

function isNumber(e: any): e is number 
    return e === undefined;


const numArry = arry.filter(isNumber);

【讨论】:

【参考方案2】:

使用 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]

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

【讨论】:

正在寻找来自社区的评论...this TS bug 表示这已得到修复,但在我看来,这种类型的守卫闻起来像黑客。什么给了?【参考方案3】:

解决方案

创建一个类型保护

function isDefined<T>(argument: T | undefined): argument is T 
    return argument !== undefined

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

const foo: number[] = [1, 2, undefined, 4].filter(isDefined)

说明

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

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

使用适当的类型保护(而不是走捷径并依赖隐式强制)有助于 TypeScript 选择这种特定的重载。

【讨论】:

【参考方案4】:

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

但是,您可以定义自己的、完全类型安全的过滤器函数,该函数接受一个数组和一个user-defined type guard function,并返回一个不同类型的数组。

不知道有多大用处,但在这里:

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;

可以这样使用:

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

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


const numArry: number[] = typeFilter(arry, isNumber);

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

【讨论】:

【参考方案5】:

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

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

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

filter(...) 签名是:

filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[];        

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

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

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

【讨论】:

感谢@Diullei,检查内置函数的原始类型定义提供了非常丰富的信息和非常好的建议。 很高兴为您提供帮助!

以上是关于告诉 TypeScript 编译器 Array.prototype.filter 从数组中删除某些类型的方法?的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript专题之类型断言

TypeScript专题之类型断言

Visual Studio - 引擎盖下(NPM/Typescript)

如何在 Typescript 中正确使用环境变量? [复制]

TypeScript(09): 函数

typescript - 8.命名空间