为什么 Array.prototype.includes(searchElement) 的参数需要与数组元素的类型相同? [英] Why does the argument for Array.prototype.includes(searchElement) need the same type as array elements?

查看:28
本文介绍了为什么 Array.prototype.includes(searchElement) 的参数需要与数组元素的类型相同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

老实说,我不知道我的设置是否有问题,或者这是否是打字稿功能.在以下示例中:

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.includes() 中的 searchElement 参数应该是安全的code> 是 T 的超类型,但是 标准 TypeScript 库声明 假定它只是 T.对于大多数目的,这是一个很好的假设,因为您通常不想像@GustavoLopes 提到的那样比较完全不相关的类型.但你的类型并非完全不相关,是吗?

有不同的方法来处理这个问题.您所做的断言可能是最不正确的断言,因为您断言 stringAllowedChars 即使它可能不是.它完成工作"但你对此感到不安是对的.


另一种方法是通过声明合并在本地覆盖标准库 接受超类型,这有点复杂,使用 条件类型:

//删除声明全局";如果您开始在全局范围内编写代码声明全局{接口阵列 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.


Playground link to code

这篇关于为什么 Array.prototype.includes(searchElement) 的参数需要与数组元素的类型相同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆