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

zphenhs4  于 2023-05-23  发布在  TypeScript
关注(0)|答案(6)|浏览(168)

我试图通过使用Array.prototype.filter从数组中过滤null(未定义)元素,但TypeScript编译器似乎无法识别“filter”函数的派生数组,无法通过类型检查。
假设下面的简化代码中有一个数组(number| undefined)[]类型,并希望筛选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);

错误说:
类型'(编号|undefined)[]'不能分配给类型'number[]'。类型'编号|undefined'不能赋值给类型' number '。类型“undefined”不能赋值给类型“number”。
我可以将结果数组转换为number[],就像下面这样知道filter函数会删除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[]);

有没有比铸造更好的方法来实现这一点?
环境:启用strictNullChecks的TSC2.1。

7eumitmz

7eumitmz1#

使用TypeScript的用户定义类型保护功能:

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结果的类型。

vnjpjtjt

vnjpjtjt2#

解决方案

创建 type guard

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

使用它作为类型 predicate :

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

说明

Array.prototype.filter有一些过载。其中一个知道返回值将取决于 predicate 函数。它使用了一个类型保护:

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

使用适当的类型保护(而不是采用快捷方式和依赖隐式强制)帮助TypeScript选择这个特定的重载。

a7qyws3x

a7qyws3x3#

以Typescript理解的类型安全方式从数组中筛选值的最简单方法是使用flatMap而不是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 ]

flatMap分两步工作。首先,它Map数组并执行你给它的任何函数。然后将其中的所有数组展平。
1.Map数组并得到:[ 1, 2, 3, 4, [], 6, 7, [], 9, 0 ]
1.展平数组中的任意数组:[ 1, 2, 3, 4, 6, 7, 9, 0 ]
瞧!现在你有了一个类型化的数组,Typescript很高兴。

qvtsj1bj

qvtsj1bj4#

可以定义自己的完全类型安全的过滤器函数,它接受数组和用户定义的类型保护函数,并返回不同类型的数组。
不知道有多有用,但它是:

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 => typeof e === 'number'也是一个类型保护。

xcitsw88

xcitsw885#

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<T>),因此返回类型将是与value参数类型相同的数组(泛型类型T)。在你的情况下,number | undefined
这就是为什么返回类型是number | undefined
在此基础上,您将需要使用强制转换表达式。如果你不希望出现这种情况,你可以删除--strictNullChecks标志。

rlcwz9us

rlcwz9us6#

解决方案

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);

相关问题