Array.filter
有两个签名:
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
filter(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): T[];
我理解第一个签名——它让类型断言谓词缩小数组的类型。
但是,第二个签名似乎使谓词返回
boolean
而不是unknown
。我知道 JS 比这更宽松,会将值强制转换为boolean
,但 TypeScript 似乎通常不鼓励隐式转换。
将其更改为
boolean
似乎可以防止更多错误,即:
// Because the return type is unknown, TS silently accepts this unintentional number | boolean return type
[1,2,3,4,5].filter(el => someMap.get(el) ?? 0 > 1) // equivalent to someMap.get(el) ?? (0 > 1)
这个签名背后的理由是什么?
同样的问题适用于
some
和any
,但不适用于find
。
filter
谓词只需要返回一个布尔值。由于 any 值可以在 JS 中强制转换为布尔值,这意味着它可以是任何类型:对我们来说它就是字面上的 unknown
。指定返回 boolean
会太狭窄:有效谓词将无法编译。
考虑一个接受所有非空字符串元素的过滤器:
let input: (string|undefined|null)[] = ["yeah", "", "okay", undefined, null];
let nonEmptyStrings = input.filter(x => x);
这个“清理”谓词是一种非常合理的过滤器,您在野外发现它不会感到惊讶。它是 JS 的“俚语”,表示更冗长、严谨的措辞,例如 ...
input.filter(x => x !=== null && x.length > 0)
input.filter(x => Boolean(x))
input.filter(x => !!x);
(我个人认为上面的第一行是改进,因为它的意图很明确。没有人会不确定是否要过滤掉空字符串。)
但在其他情况下,冗长是否有帮助则不太清楚,例如在检查属性是否存在时。比较这两个...
input.filter(x => x.someOptionalProperty) // not type safe, nice to read
input.filter(x => Object.hasOwnProperty('someOptionalProperty')) // type safe, yucky
不知道你,但我非常更喜欢前者。它更短,惯用,你可以 IDE 上下文(“去定义”)。
但比非布尔返回是否是“好风格”更重要的是,TS 的目标之一是提供 所有有效 JS 的结构化类型,而不是希望尘埃和蜘蛛网消失。过滤谓词可以有效地返回 any value 是没有争议的,所以结果确实必须是
unknown
.
(也许有一天 TS 会支持一个
booleanish
糖类型;像 unknown
但期望被解释为布尔值?这可能会使签名的动机更加清晰。)