Bug报告
我搜索了,但找不到相关的issue(但我觉得很可能不存在这样的问题)。似乎应该有一个标签为“设计限制”之类的东西?
基本问题是,如果你使用一个与reducer返回值不同的标准“reducer”回调模式,返回的类型就是不正确的。
// The type of result is `number`, but should be `string`.
const result = [1, 2, 3].reduce((acc, value) => {
// ~~~~~~~~~~~~~~~~~
// ^-- Error Here: "No overload matches this call"
if (acc === null) {
return '' + value;
} else {
return acc + ', ' + value;
}
}, null);
console.log(result); // "1, 2, 3"
console.log(typeof result); // "string"
🔎 搜索词
is:issue is:open array reduce
is:issue is:open reduce callback
is:issue is:open reduce "No overload matches this call"
is:issue is:open reduce label:"Design Limitation"
🕗 版本与回归信息
任何版本的TS,到TMK。具体在4.2和4.4.4中测试过。
- 我尝试了每个版本,并尽可能查阅了FAQ中的条目。显然有很多条目。我没有看到任何相关的。
⏯ Playground链接
Playground中的示例
💻 代码
// The type of result is `number`, but should be `string`.
const result = [1, 2, 3].reduce((acc, value) => {
// ~~~~~~~~~~~~~~~~~
// ^-- Error Here: "No overload matches this call"
if (acc === null) {
return '' + value;
} else {
return acc + ', ' + value;
}
}, null);
console.log(result); // "1, 2, 3"
console.log(typeof result); // "string"
🙁 实际行为
即使编译后的JavaScript是有效的,也会报类型错误。
🙂 预期行为
没有类型错误,result
应该是一个类型 string
,而不是 number
。
8条答案
按热度按时间hmmo2u0o1#
这是一个推理问题,而不是打字问题:https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAeWoUKgAPqFHipAPgAU-gCGysrsAG5BhPjQAJSgOr6gAN6KIKAZmVnZObkZAH6FRcUlhWlgeZVV2QB6ALR1oACiWFioWKAAEszQAFygAEQAcqigqOHMhKhBgnRBcMowFHCQ5KDKUYQDilmkmMGhCTp6LoTxqQp5OHD4WNSgAOQPoADUoJHR0ADcO5kAvqBoIRcLALldoDc7qAQspXo92M83h8Yj9Lv9FH92KdYqiVGpcKhCNAZFNJP4cARiDjQOkBsZQGZQOZtgpVOpCcTSf5ECgMJpKXBqbTvBJJAMgA
翻译结果为:这是一个推理问题,而不是打字问题。
h7appiyu2#
I think this is covered by #36554 . I usually handle this with something similar to @nmain’s example, by adding an annotation on the accumulator. https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAKRwENlygFyhahQqAA+oKLiUuwAbi6E+NAAlKA6AHygAN6KoOlkmK7u8Tp6PoRxqQoZpZpw+FjUoADkNaAA1KARUdAA3GkZAL6g0IS4sMVlGTgVVaBuyo217PVNLdEdJd2KXewFMUsqariohNAyhKiSjjgExJugIKAARMagZqDmN9vqewdHJ4goGJrncJdrjdghJJDcgA
b09cbbtk3#
在#36554中有一些关于reduce的提及,但我建议你将这个示例也复制到那里以获得更多的覆盖范围。谢谢!
zlhcx6iw4#
我们的推断过程在这里有着根本性的不同,但当我们有普通的局部语句时,我们能够深入到这个循环的主体中,并知道它已经被设置为
string
或null
。我想在某个时候,我想请教一下 @ahejlsberg ,了解如何处理这些循环控制流分析(我已经有一段时间没有接触了,所以我基本上已经忘记了)。也许我们可以就一些更适合这种高阶函数的东西进行头脑 Storm 。我们推断过程的一个问题是,在我们第一次遍历参数时,我们“锁定”了那些不是“上下文敏感”的表达式的推断(例如回调函数)。这里的
null
是唯一一个不是上下文敏感的东西,所以它赢了。我们可以尝试对这个过程进行调整,但是eivnm1vs5#
这个问题已经被标记为"重复",并且最近没有活动。它已经自动关闭,以进行维护。
o2gm4chl6#
是的,我对@nmain的方法很熟悉。然而,我强烈建议人们利用推断而不是传递显式泛型。我说这个是因为泛型只是API签名中的另一个元素,可以在主要版本之间发生变化。所以
reduce((acc: null | string, item) =>
会成为我的首选,而不是<null | string>
。这意味着泛型是隐式的,你不必担心它们在发布中是否会发生改变。myzjeezk7#
老实说,@DanielRosenwasser,我更希望这个问题保持开放,因为我觉得这个问题是reduce类型的回调情况中独一无二的。正如我们在RxJS的
scan
中所看到的,这显然与数组方法无关。在这里无法正确进行推断,我亲眼看到过数百个例子,人们用不准确的显式类型来实现这个功能,结果却是自讨苦吃。siv3szwd8#
@benlesh,你是在询问是否可以自动进行联合类型提升吗?
因为在示例中展示的代码里,没有什么是不可能的。用户可以通过以下方式轻松地修复类型错误:
''
(空字符串),而不是null
- 这样所有三种类型(累加器、返回值、默认值)都是相同的,整个代码会有一个漂亮的签名reduce<A, T>((a: A, v: T) => A, default: A
。null
的函数体代码。这对于你展示的例子(if(!acc)
)来说不是问题,但对于更复杂的实际代码可能会更棘手。然而,原始问题在于在一个应该只有一个类型的场景中使用了两个类型。https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAKRwENly9gDcXhfNACUoDoAfKAA3oogoNExsXHxCdEAfimpaekpkWCJOblxAHoAtIWgAKJYWKhYoAASzNAAXKAARAByqKConsyEqC6CdC5wyjAUcJDkoMo+hM2KsaSYru5BOnq0hISBEQqJOHD4WNSgAOQnoADUoN6+0ADc8zEAvqDQhLiwO3vQB0egbspLqd2Ocrjc-A9ds9FE8QSd-JCVGpcKhCNAZL1JI4cARiAjQFFmsZQGZQOY5gpVOpUejMY5ECgMJpcXB8YTROIpM0gA
ps:我知道很多人可能不同意放弃使用 null 作为默认值,所以那些可以在 reduce/scan 函数调用末尾使用
, null as null|string);
类型。Typescript 如何(以及为什么应该)将
null
提升为联合类型 - 这是另一个问题,不过..reduce<A,T,D>(a:A|D, v:T) => A, default: A|D)
看起来已经很混乱了,如果这种提升只会针对 null 进行特殊处理(通过条件类型),那么它就变得几乎神奇(用不好的话说)。然而,如果这能帮助人们更准确地描述他们的代码 - 那么或许这就是前进的方向🤷