老实说,我不知道我的设置是否有问题,或者这是否是打字稿功能。在以下示例中:
type AllowedChars = 'x' | 'y' | 'z';
const exampleArr: AllowedChars[] = ['x', 'y', 'z'];
function checkKey(e: KeyboardEvent) {
if (exampleArr.includes(e.key)) { // <-- here
// ...
}
}
打字稿编译器抱怨
Argument of type 'string' is not assignable to parameter of type 'AllowedChars'.
但是我在哪里分配? Array.prototype.includes
返回一个布尔值(我没有存储)。我可以通过类型断言来消除错误,如下所示:
if (exampleArr.includes(e.key as AllowedChars)) {}
但这怎么是正确的,我期待用户输入,它可以是任何东西。我不明白为什么用于检查
if在数组中找到元素的函数(
Array.prototype.includes()
)应该了解期望的输入类型。
我的
tsconfig.json
(打字稿v3.1.3):
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
"allowJs": true,
"noEmit": true,
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "preserve",
},
"include": [
"src"
],
"exclude": [
"node_modules",
"**/__tests__/**"
]
}
如有任何帮助,我们将不胜感激!
有关 Array.prototype.includes()
和超类型的完整讨论,请参阅
microsoft/TypeScript#26255。
是的,从技术上讲,允许
searchElement
中的 Array<T>.includes()
参数成为 T
的超类型应该是安全的,但 标准 TypeScript 库声明 假设它只是 T
。对于大多数目的来说,这是一个很好的假设,因为您通常不想像 @GustavoLopes 提到的那样比较完全不相关的类型。但你的类型并非完全无关,不是吗?
有不同的方法来处理这个问题。您所做的断言可能是最不正确的断言,因为您断言
string
是 AllowedChars
,即使它可能不是。它“完成了工作”,但你对此感到不安是对的。
另一种方法是通过 声明合并 本地覆盖标准库以接受超类型,这有点复杂,因为 TypeScript 不支持超类型约束(有关功能请求,请参阅 ms/TS#14520)。相反,声明使用条件类型来模拟超类型约束:
// remove "declare global" if you are writing your code in global scope to begin with
declare global {
interface Array<T> {
includes<U extends (T extends U ? unknown : never)>(
searchElement: U, fromIndex?: number): boolean;
}
}
然后你的原始代码就可以工作了:
if (exampleArr.includes(e.key)) {} // okay
// call to includes inspects as
// (method) Array<AllowedChars>.includes<string>(
// searchElement: string, fromIndex?: number | undefined): boolean (+1 overload)
同时仍然防止完全不相关的类型的比较:
if (exampleArr.includes(123)) {} // error
// Argument of type '123' is not assignable to parameter of type 'AllowedChars'.
但是处理这个问题最简单且仍然正确的方法是将
exampleArr
的类型扩大为 readonly string[]
:
const stringArr: readonly string[] = exampleArr; // no assertion
if (stringArr.includes(e.key)) {} // okay
或更简洁地说:
if ((exampleArr as readonly string[]).includes(e.key)) {} // okay
扩大到
readonly string[]
没问题,但要小心扩大到 string[]
,这有点危险,因为为了方便起见,TypeScript 不安全地将 Array<T>
视为 T
中的 covariant。这对于阅读来说很好,但是当你编写属性时,你会遇到问题:
(exampleArr as string[]).push("oops"); // actually writing to an AllowedChars[]
但是由于您只是从数组中读取,所以它是完全安全的,这就是为什么建议使用
readonly
。
如果你比较两种不同类型,那么它们自然是不同的。
想象你有:
type A = {paramA: string};
type B = {paramB: number};
const valuesA: A[] = [{paramA: 'whatever'}];
const valueB: B = {paramB: 5};
valuesA.includes(valueB); // This will always be false, so it does not even make sense
在您的情况下,编译器将
AllowedChars
视为与 string
完全不同的类型。您必须将收到的 string
“投射”到 AllowedChars
。
但这如何正确,我期待用户输入,它可以是任何东西。
编译器不知道你想用
includes
来完成什么。它只知道它们有不同的类型,因此不应该比较它们。
最终只使用
.some
而不是 .includes
就帮了我的忙。这里是游乐场。
const FRUITS = ["apple", "banana", "orange"] as const
type Fruit = typeof FRUITS[number]
const isFruit = (value: string): value is Fruit =>
FRUITS.some(fruit => fruit === value)