为什么此代码段无效 TypeScript?
Why is this snippet invalid 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 中编译此代码段时,出现以下错误:
Type 'string' is not assignable to type '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,第一个代码段无效而其他两个代码段正常,这让我感到非常困惑。看起来类型推断在第一个片段中以某种方式失败了。他们不应该都是等价的吗?
这是 TypeScript 的设计限制;有关详细信息,请参阅 microsoft/TypeScript#46707。这是一种级联故障,会导致一些奇怪的错误。
在 microsoft/TypeScript#29478 allowing the compiler to use the expected return type of a generic function contextually 中实现了一个普遍有用的功能,用于将上下文类型传播回通用函数的参数。这可能没有多大意义,但它看起来像这样:
function foo<T>(x: [T]): T { return x[0] }
const bar = foo(["x"]);
// function foo<string>(x: [string]): string
const baz: "x" | "y" = foo(["x"]);
// function foo<"x">(x: ["x"]): "x"
请注意 bar
的类型是 string
,因为对 foo(["x"])
的调用将类型参数 T
推断为 string
。但是 baz
已经 annotated to be of type "x" | "y"
, and this gives the compiler some context that we want the T
type in the call to foo(["x"])
to be narrower than string
, so it infers it as the string literal type "x"
.
此行为在 TypeScript 中有很多积极影响,但也存在一些问题。你运行陷入其中一个问题。
TypeScript typings for Array.prototype.reduce()
看起来像这样:
interface Array<T> {
reduce(cb: (prev: T, curr: T, idx: number, arr: T[]) => T): T;
reduce(cb: (prev: T, curr: T, idx: number, arr: T[]) => T, init: T): T;
reduce<U>(cb: (prev: U, curr: T, idx: number, arr: T[]) => U, init: U): U;
}
这是一个 overloaded 方法,具有多个调用签名。第一个不相关,因为它没有 init
参数。第二个是 non-generic,并期望累加器与数组元素的类型相同。第三个是通用的,允许您为累加器指定不同的类型 U
。
您在这里想要的是第三个通用调用签名。让我们假设这是编译器选择的那个。在您对 reduce()
的调用中,编译器期望 return 类型为 boolean
。因此泛型类型参数 U
有一个 boolean
上下文,并且该上下文类型根据 ms/TS#29478 传播回 init
参数。编译器将 init
参数视为 true
并将其推断为文字 true
类型而不是 boolean
.
这是第一个问题,因为你的回调return是acc && sentence.includes(word)
,也就是boolean
不一定是true
:
let b: boolean = fullText.reduce(
(acc, sentence) => acc && sentence.includes(word), // error!
// --------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// boolean is not assignable to true
true
);
/* (method) Array<string>.reduce<true>(
cb: (prev: true, curr: string, idx: number, arr: string[]) => true,
init: true
): true */
如果 reduce()
只有一个调用签名,您将看到此错误。也许从这个角度来看,采用为 init
编写 true as boolean
的变通方法,或者手动将 U
指定为 boolean
,如 fullText.reduce<boolean>(...)
].
但不幸的是,其他事情正在发生,这使得错误不太明显。
编译器不仅尝试通用调用签名;它还会尝试 non-generic 一个,其中 init
预计与数组元素的类型相同。这个也失败了,这次是有充分理由的,因为这意味着 init
必须是 string
,而回调 return 是 string
,并且reduce()
return 是 string
,这在很多方面都是错误的:
b = fullText.reduce( // error!
// <-- string is not assignable to boolean
(acc, sentence) => acc && sentence.includes(word), // error!
// --------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// boolean is not assignable to string
true);
/* (method) Array<string>.reduce(
cb: (prev: string, curr: string, idx: number, arr: string[]) => string,
init: string
): string */
编译器尝试了两个重载的调用签名,但都失败了。如果您仔细查看错误消息,编译器会抱怨它们:
/* No overload matches this call.
Overload 1 of 3, '(cb: (prev: string, curr: string, idx: number, array: string[]) => string,
init: string): string', gave the following error.
Type 'boolean' is not assignable to type 'string'.
Overload 2 of 3, '(cb: (prev: true, curr: string, idx: number, array: string[]) => true,
init: true): true', gave the following error.
Type 'boolean' is not assignable to type 'true'. */
一般来说,当所有重载都失败时,编译器会选择最早的一个来使用。不幸的是,这意味着 returns string
的调用签名是编译器决定的。所以 return
语句显然是 return 在你的 containsWord()
函数中 string
是 annotated 到 return 一个 boolean
. 那是导致您感到困惑的错误的原因:
Type 'string' is not assignable to type 'boolean'.
奇怪!
考虑以下 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 中编译此代码段时,出现以下错误:
Type 'string' is not assignable to type '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,第一个代码段无效而其他两个代码段正常,这让我感到非常困惑。看起来类型推断在第一个片段中以某种方式失败了。他们不应该都是等价的吗?
这是 TypeScript 的设计限制;有关详细信息,请参阅 microsoft/TypeScript#46707。这是一种级联故障,会导致一些奇怪的错误。
在 microsoft/TypeScript#29478 allowing the compiler to use the expected return type of a generic function contextually 中实现了一个普遍有用的功能,用于将上下文类型传播回通用函数的参数。这可能没有多大意义,但它看起来像这样:
function foo<T>(x: [T]): T { return x[0] }
const bar = foo(["x"]);
// function foo<string>(x: [string]): string
const baz: "x" | "y" = foo(["x"]);
// function foo<"x">(x: ["x"]): "x"
请注意 bar
的类型是 string
,因为对 foo(["x"])
的调用将类型参数 T
推断为 string
。但是 baz
已经 annotated to be of type "x" | "y"
, and this gives the compiler some context that we want the T
type in the call to foo(["x"])
to be narrower than string
, so it infers it as the string literal type "x"
.
此行为在 TypeScript 中有很多积极影响,但也存在一些问题。你运行陷入其中一个问题。
TypeScript typings for Array.prototype.reduce()
看起来像这样:
interface Array<T> {
reduce(cb: (prev: T, curr: T, idx: number, arr: T[]) => T): T;
reduce(cb: (prev: T, curr: T, idx: number, arr: T[]) => T, init: T): T;
reduce<U>(cb: (prev: U, curr: T, idx: number, arr: T[]) => U, init: U): U;
}
这是一个 overloaded 方法,具有多个调用签名。第一个不相关,因为它没有 init
参数。第二个是 non-generic,并期望累加器与数组元素的类型相同。第三个是通用的,允许您为累加器指定不同的类型 U
。
您在这里想要的是第三个通用调用签名。让我们假设这是编译器选择的那个。在您对 reduce()
的调用中,编译器期望 return 类型为 boolean
。因此泛型类型参数 U
有一个 boolean
上下文,并且该上下文类型根据 ms/TS#29478 传播回 init
参数。编译器将 init
参数视为 true
并将其推断为文字 true
类型而不是 boolean
.
这是第一个问题,因为你的回调return是acc && sentence.includes(word)
,也就是boolean
不一定是true
:
let b: boolean = fullText.reduce(
(acc, sentence) => acc && sentence.includes(word), // error!
// --------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// boolean is not assignable to true
true
);
/* (method) Array<string>.reduce<true>(
cb: (prev: true, curr: string, idx: number, arr: string[]) => true,
init: true
): true */
如果 reduce()
只有一个调用签名,您将看到此错误。也许从这个角度来看,采用为 init
编写 true as boolean
的变通方法,或者手动将 U
指定为 boolean
,如 fullText.reduce<boolean>(...)
].
但不幸的是,其他事情正在发生,这使得错误不太明显。
编译器不仅尝试通用调用签名;它还会尝试 non-generic 一个,其中 init
预计与数组元素的类型相同。这个也失败了,这次是有充分理由的,因为这意味着 init
必须是 string
,而回调 return 是 string
,并且reduce()
return 是 string
,这在很多方面都是错误的:
b = fullText.reduce( // error!
// <-- string is not assignable to boolean
(acc, sentence) => acc && sentence.includes(word), // error!
// --------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// boolean is not assignable to string
true);
/* (method) Array<string>.reduce(
cb: (prev: string, curr: string, idx: number, arr: string[]) => string,
init: string
): string */
编译器尝试了两个重载的调用签名,但都失败了。如果您仔细查看错误消息,编译器会抱怨它们:
/* No overload matches this call.
Overload 1 of 3, '(cb: (prev: string, curr: string, idx: number, array: string[]) => string,
init: string): string', gave the following error.
Type 'boolean' is not assignable to type 'string'.
Overload 2 of 3, '(cb: (prev: true, curr: string, idx: number, array: string[]) => true,
init: true): true', gave the following error.
Type 'boolean' is not assignable to type 'true'. */
一般来说,当所有重载都失败时,编译器会选择最早的一个来使用。不幸的是,这意味着 returns string
的调用签名是编译器决定的。所以 return
语句显然是 return 在你的 containsWord()
函数中 string
是 annotated 到 return 一个 boolean
. 那是导致您感到困惑的错误的原因:
Type 'string' is not assignable to type 'boolean'.
奇怪!