为什么此代码片段的 TypeScript 无效?
考虑以下 TypeScript 片段:
function containsWord(): boolean {
const fullText: string[] = ['This is a sentence with seven words'];
const word: string = 'word';
return (
fullText.reduce((acc, sentence) => acc && sentence.includes(word), true)
);
}
console.log(containsWord())
当尝试在 VS Code 或 TypeScript Playground,我收到以下错误:
类型“string”不可分配给类型“boolean”
我不太明白。真正令人惊讶的是,将 return 语句的内容移至变量可以解决该问题:
function containsWord(): boolean {
const fullText: string[] = ['This is a sentence with seven words'];
const word: string = 'word';
const result = fullText.reduce((acc, sentence) => acc && sentence.includes(word), true);
return result;
}
console.log(containsWord());
或者显式设置回调参数类型:
function containsWord(): boolean {
const fullText: string[] = ['This is a sentence with seven words'];
const word: string = 'word';
return (
fullText.reduce((acc: boolean, sentence: string) => acc && sentence.includes(word), true)
);
}
console.log(containsWord());
根据 TypeScript,第一个片段无效,而其他两个片段则很好,这一事实让我感到非常困惑。看起来类型推断在第一个片段中以某种方式失败了。它们不应该都是等价的吗?
Consider the following TypeScript snippet:
function containsWord(): boolean {
const fullText: string[] = ['This is a sentence with seven words'];
const word: string = 'word';
return (
fullText.reduce((acc, sentence) => acc && sentence.includes(word), true)
);
}
console.log(containsWord())
When trying to compile this snippet in VS Code or in the TypeScript playground, I get the following error:
Type 'string' is not assignable to type 'boolean'
Which I don't really understand. What's really surprising is that moving the content of the return statement to a variable fixes the issue:
function containsWord(): boolean {
const fullText: string[] = ['This is a sentence with seven words'];
const word: string = 'word';
const result = fullText.reduce((acc, sentence) => acc && sentence.includes(word), true);
return result;
}
console.log(containsWord());
Or explicitly setting the callback arguments types:
function containsWord(): boolean {
const fullText: string[] = ['This is a sentence with seven words'];
const word: string = 'word';
return (
fullText.reduce((acc: boolean, sentence: string) => acc && sentence.includes(word), true)
);
}
console.log(containsWord());
I'm very confused by the fact that the first snippet is invalid while the two others are fine according to TypeScript. It looks like type inference is somehow failing in the first snippet. Shouldn't they all be equivalent?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
TS4.7+ 的更新
这是 TypeScript 的设计限制;有关详细信息,请参阅 microsoft/TypeScript#46707。 microsoft/TypeScript#48380 已修复 TypeScript 4.7。
TS4.6 的先前答案 -
这是 TypeScript 中的设计限制;有关详细信息,请参阅 microsoft/TypeScript#46707。这是一种级联故障,会导致一些奇怪的错误。
这是 TypeScript 的设计限制;有关详细信息,请参阅 microsoft/TypeScript#46707。这是一种级联故障,会导致一些奇怪的错误。
microsoft/TypeScript#29478 中实现了一个普遍有用的功能,允许编译器使用generic 函数的预期返回类型 上下文 将上下文类型传播回泛型函数的参数。这可能没有多大意义,但它看起来像这样:
注意
bar
的类型是string
,因为调用foo(["x "])
将类型参数T
推断为string
。但是baz
已经注释 为"x" | 类型“y”
,这为编译器提供了一些上下文,表明我们希望调用foo(["x"])
时的T
类型更窄比string
,因此它将其推断为字符串 字面意思输入“x”
。这种行为在 TypeScript 中有很多积极的影响,但也存在一些问题。你遇到了问题之一。
数组的 TypeScript 类型.prototype.reduce()
看起来像这是一个重载方法,有多个调用签名。第一个不相关,因为它没有
init
参数。第二个是非通用的,并且期望累加器与数组元素具有相同的类型。第三个是通用的,允许您为累加器指定不同的类型,U
。您需要的是第三个通用调用签名。让我们想象一下这是编译器选择的。在调用
reduce()
时,编译器期望返回类型为boolean
。因此,泛型类型参数U
具有一个boolean
上下文,并且该上下文类型按照 ms/TS#29478 传播回init
参数。编译器会查看init
参数是否为true
,并推断其为文字true
类型,而不是boolean
。这是第一个问题,因为你的回调返回
acc && Sentence.includes(word)
,它是boolean
,不一定是true
:如果使用
reduce()
,这是您会看到的错误> 只有一个呼叫签名。也许由此看来,采用将init
写入true as boolean
或手动指定U
的解决方法至少是模糊直观的。是boolean
就像fullText.reduce(...)
。但不幸的是,其他事情正在发生,使得错误变得不那么明显。
编译器不仅会尝试通用调用签名,还会尝试通用调用签名。它还尝试非泛型,其中
init
预计与数组元素具有相同的类型。这一次也失败了,这次有充分的理由,因为这意味着init
必须是string
,并且回调返回一个string< /code>,并且
reduce()
返回一个string
,这在很多方面都是错误的:编译器尝试了两个重载的调用签名,但它们都失败了。如果仔细查看错误消息,编译器会抱怨这两个错误消息:
一般来说,当所有重载失败时,编译器会选择最早使用的一个。不幸的是,这意味着返回
string
的调用签名是编译器决定发生的。因此,return
语句显然是在containsWord()
函数中返回一个string
,即 注释以返回布尔值
。这就是导致您困惑的错误的原因:奇怪!
<一href="https://www.typescriptlang.org/play?ts=4.5.4#code/JYOwLgpgTgZghgYwgAgIJSnAngHgCoB8yA3gLA BQyVyA5hCAEoQAmArkjgKoEAUCARgC5kPAA5QIAN2GcanMgSsMwvPODMAHsJCsAtv2jy4y5HgDaAXQCUyALxE5yUMDAyrMg NwVqyEAHsQAHF6JjYkPiERcSkVeUUTVSdNbT0DKCMEyxt7UzUQFxV3Uy9yAF8KdEwsADpxPzB6rFEIarpGFnYUWzQMbFqo esbm6okwiBLKvrqGsCaW-yCQjqQ7Hqr+wdnh0c6SihhWEAQwYACFALA4UABnAHU-KGYeIv4-PwAbCDgQEm9qBAC1zAyAO73 eeAgGlcyCBUFANEsqzMAHI8AALYDXJxYuAw+IQI4oADuLjReMk9GQRIezGuyIse0o-0BwOpj2EsPhq2RbOYyMZPk+wMirw +XX+3VB4MhYFaSzGPD+PioPEQCHk13x9CQ2SIauQADIDXjwNqWqAEO9WMwINceLyrPIAPRO5DQAZQACESuVLuQAFpA0Hg8G IAA-CORqPRmOxiM+nx+0Wfb7Y3z1ZBwa7XYA0EBwfifZANYtQVgQBPUMBlitMqhWEqJgBUIl0EDAaL8zBsk1wnJANAI1UgQ NCNRw1fLvE-0iYgk0lL5biSigHor8LUyV8qUMmZM-YR1jsRENEHKM6ozmhZ5nRTPyCbTooPv4qylEKh1QWwXaCuQfrug83 p1r6ro4IGMIbgOab+MCWY5nmBZFiWybipeqoIOqJoEjqJ6ZlhhrGpqpqEtUFpWjadoOs6rpAV6l5+iGzFBuGcbsRx8agYm rpoammLpvB2a5vmhYoCWh6XmeDY+k6LY8G2HZdj2vR9tBg7fgEv5juEl4CMI84xFBcIDiuB7qVuWg7voe7GGuxnwlk+GHhe 3HUNe64mTQlZFIej7PuQL6gRIYBKD8ipuVQH4yiMywQDwmHYSRuEQLqBEIEROFmuRRyUba9o0o6S6pT6MmgXJyAAHJ+Mgf GULA7x+HAzDILocBgAgaK2sWGJYggcBgtUfwAPL1Y1zXIAAjLVMDIAAzPIyIRAZ0SLi5Cirp5m5JFZOg2ek+5VFTA5OTk60 eQ5A6+epyLyDQcAUj1KAwB8jUkjB9FDaBeBzMgyJ8SAyKwRmCEich4k1VsKDIoeyJfVQo3QONLUAEwzfNi3LVEC7CGeZn2 ed257WkGRHZdR5pXjTj5DeNb3jWt20A94ldSCr1+O9NBuhgDzw9QP3NH9ANAwJcGZsJSFicWkO-ciZ5wzwKMAOwAGwAJw2E +FDlIF5AAiA1xitUjU0HwFxXAb9yPM8VhBfshzHKcPwvX4+C8FZZh4NYKgkMgIVhcgGhmaAADBYyA6xQ+tAsg-DGO+bw8GYA BEGhJ9YJR+gcRwnGcLs4Ie7vCGYh7e+TQVR8KcAAF7CCnSfIAAPsgSdYPXkoJ8nqfpxQmcOznztvDgdeF8gndp0UddBUAA" rel="nofollow noreferrer">Playground 代码链接
UPDATE FOR TS4.7+
This was a design limitation in TypeScript; see microsoft/TypeScript#46707 for details. It was fixed for TypeScript 4.7 by microsoft/TypeScript#48380.
PREVIOUS ANSWER FOR TS4.6-
This is a design limitation in TypeScript; see microsoft/TypeScript#46707 for details. It's sort of a cascading failure that results in some weird errors.
This is a design limitation in TypeScript; see microsoft/TypeScript#46707 for details. It's sort of a cascading failure that results in some weird errors.
A generally useful feature was implemented in microsoft/TypeScript#29478 allowing the compiler to use the expected return type of a generic function contextually to propagate a contextual type back to the generic function's arguments. That might not make much sense, but it sort of looks like this:
Note how the type of
bar
isstring
, since the call tofoo(["x"])
infers the type parameterT
as juststring
. Butbaz
has been annotated to be of type"x" | "y"
, and this gives the compiler some context that we want theT
type in the call tofoo(["x"])
to be narrower thanstring
, so it infers it as the string literal type"x"
.This behavior has a lot of positive effects in TypeScript, and a few problems. You ran into one of the problems.
The TypeScript typings for
Array.prototype.reduce()
look like this:That's an overloaded method, with multiple call signatures. The first one is not relevant, since it doesn't have an
init
parameter. The second one is non-generic, and expects the accumulator to be of the same type as the array elements. The third one is generic and lets you specify a different type,U
, for the accumulator.The one you want here is that third, generic call signature. Let's imagine that's the one the compiler chooses. In your call to
reduce()
, the compiler expects the return type to beboolean
. Therefore the generic type parameterU
has aboolean
context, and that contextual type propagates back to theinit
parameter as per ms/TS#29478. The compiler looks at theinit
parameter beingtrue
and infers it to be the literaltrue
type instead ofboolean
.Which is the first problem, because your callback returns
acc && sentence.includes(word)
, which isboolean
and not necessarilytrue
:This is the error you would see if
reduce()
only had the one call signature. And maybe from this it would be at least vaguely intuitive to employ a the workaround of writingtrue as boolean
forinit
, or manually specifyingU
to beboolean
likefullText.reduce<boolean>(...)
.But unfortunately other stuff is going on which makes the error less obvious.
The compiler doesn't only try the generic call signature; it also tries the non-generic one, where
init
is expected to be the same type as the array elements. This one also failed, and this time for good reason, since it would mean thatinit
would have to be astring
, and that the callback returns astring
, and thatreduce()
returns astring
, which is wrong in a bunch of ways:The compiler tries both overloaded call signatures and they both fail. If you look carefully at the error messages, the compiler is complaining about both of them:
Generally speaking when all overloads fail, the compiler picks the earliest one to use. And that unfortunately means that the call signature that returns
string
is what the compiler decides happens. So thereturn
statement is, apparently, returning astring
in yourcontainsWord()
function which is annotated to return aboolean
. And that's what causes the error that's confusing you:Bizarre!
Playground link to code