为什么 Array.prototype.includes(searchElement) 的参数需要与数组元素的类型相同? [英] Why does the argument for Array.prototype.includes(searchElement) need the same type as array elements?
问题描述
老实说,我不知道我的设置是否有问题,或者这是否是打字稿功能.在以下示例中:
type AllowedChars = 'x' |'y' |'z';const exampleArr: AllowedChars[] = ['x', 'y', 'z'];功能检查键(e:KeyboardEvent){if (exampleArr.includes(e.key)) {//<-- 这里//...}}
打字稿编译器抱怨 'string' 类型的参数不能分配给 'AllowedChars' 类型的参数.
但是我在哪里分配?Array.prototype.includes
返回一个布尔值(我没有存储).我可以通过类型断言消除错误,如下所示:
if (exampleArr.includes(e.key as AllowedChars)) {}
但这如何正确,我期待用户输入可以是任何内容.我不明白为什么一个函数 (Array.prototype.includes()
) 用于检查 if 在数组中找到元素,应该了解数组的类型期望的输入.
我的tsconfig.json
(打字稿v3.1.3):
<代码> {编译器选项":{"目标": "esnext","moduleResolution": "节点",allowJs":真,noEmit":真,严格":真实,isolatedModules":真,"esModuleInterop": 真,"jsx": "保留",},包括": [源代码"],排除": [节点模块",**/__测试__/**"]}
任何帮助将不胜感激!
是的,从技术上讲,允许 Array
中的 searchElement
参数应该是安全的code> 是 T
的超类型,但是 标准 TypeScript 库声明 假定它只是 T
.对于大多数目的,这是一个很好的假设,因为您通常不想像@GustavoLopes 提到的那样比较完全不相关的类型.但你的类型并非完全不相关,是吗?
有不同的方法来处理这个问题.您所做的断言可能是最不正确的断言,因为您断言 string
是 AllowedChars
即使它可能不是.它完成工作"但你对此感到不安是对的.
另一种方法是通过声明合并在本地覆盖标准库 接受超类型,这有点复杂,使用 条件类型:
//删除声明全局";如果您开始在全局范围内编写代码声明全局{接口阵列 T{包括<U extends (T extends U ? unknown : never)>(searchElement: U, fromIndex?: number): boolean;}}
然后您的原始代码将起作用:
if (exampleArr.includes(e.key)) {}//好的//调用包含检查为//(方法) Array.includes(searchElement: string, fromIndex?: number | undefined): boolean (+1 重载)
同时仍然阻止比较完全不相关的类型:
if (exampleArr.includes(123)) {}//错误//'123' 类型的参数不能分配给 'AllowedChars' 类型的参数.
但是处理这个问题的最简单且仍然正确的方法是将 exampleArr
的类型扩大到 string[]
:
const stringArr: string[] = exampleArr;//没有断言if (stringArr.includes(e.key)) {}//好的
或者更简洁地说:
if ((exampleArr as string[]).includes(e.key)) {}//好的
扩展到 string[]
只是一种"正确,因为 TypeScript 不安全地将 Array<T>
视为 协变 在 T
中为方便起见.这很适合阅读,但是当你写属性时你会遇到问题:
(exampleArr as string[]).push("whoopsie");//哦哦
但由于您只是从数组中读取数据,因此是完全安全的.
游乐场连结代码 >
I honestly don't know if something is wrong with my settings or of if this is a typescript feature. In the following example:
type AllowedChars = 'x' | 'y' | 'z';
const exampleArr: AllowedChars[] = ['x', 'y', 'z'];
function checkKey(e: KeyboardEvent) {
if (exampleArr.includes(e.key)) { // <-- here
// ...
}
}
The typescript compiler complains that Argument of type 'string' is not assignable to parameter of type 'AllowedChars'.
But where am I assigning? Array.prototype.includes
returns a boolean (which I am not storing). I could silence the error by a type assertion, like this:
if (exampleArr.includes(e.key as AllowedChars)) {}
But how is that correct, I am expecing user input which could be anything. I don't understand why a function (Array.prototype.includes()
) made to check if an element is found in an array, should have knowledge about the type of input to expect.
My tsconfig.json
(typescript 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__/**"
]
}
Any help would be appreciated!
Yes, technically it should be safe to allow the searchElement
parameter in Array<T>.includes()
to be a supertype of T
, but the standard TypeScript library declaration assumes that it is just T
. For most purposes, this is a good assumption, since you don't usually want to compare completely unrelated types as @GustavoLopes mentions. But your type isn't completely unrelated, is it?
There are different ways to deal with this. The assertion you've made is probably the least correct one because you are asserting that a string
is an AllowedChars
even though it might not be. It "gets the job done" but you're right to feel uneasy about it.
Another way is to locally override the standard library via declaration merging to accept supertypes, which is a bit complicated and uses conditional types:
// 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;
}
}
Then your original code will just work:
if (exampleArr.includes(e.key)) {} // okay
// call to includes inspects as
// (method) Array<AllowedChars>.includes<string>(searchElement: string, fromIndex?: number | undefined): boolean (+1 overload)
while still preventing the comparison of completely unrelated types:
if (exampleArr.includes(123)) {} // error
// Argument of type '123' is not assignable to parameter of type 'AllowedChars'.
But the easiest and still kind-of-correct way to deal with this is to widen the type of exampleArr
to string[]
:
const stringArr: string[] = exampleArr; // no assertion
if (stringArr.includes(e.key)) {} // okay
Or more succinctly like:
if ((exampleArr as string[]).includes(e.key)) {} // okay
Widening to string[]
is only "kind of" correct because TypeScript unsafely treats Array<T>
as covariant in T
for convenience. This is fine for reading, but when you write properties you run into problems:
(exampleArr as string[]).push("whoopsie"); // uh oh
But since you're just reading from the array it's perfectly safe.
这篇关于为什么 Array.prototype.includes(searchElement) 的参数需要与数组元素的类型相同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!