为什么 Array.prototype.includes(searchElement) 的参数需要与数组元素的类型相同?

IT技术 javascript typescript
2021-01-27 03:50:36

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

type AllowedChars = 'x' | 'y' | 'z';
const exampleArr: AllowedChars[] = ['x', 'y', 'z'];

function checkKey(e: KeyboardEvent) { 
    if (exampleArr.includes(e.key)) { // <-- here
        // ...
    } 
}

typescript编译器抱怨Argument of type 'string' is not assignable to parameter of type 'AllowedChars'.但是我在哪里分配?Array.prototype.includes返回一个布尔值(我没有存储)。我可以通过类型断言消除错误,如下所示:

if (exampleArr.includes(e.key as AllowedChars)) {}

但这如何正确,我期待用户输入可以是任何内容。我不明白为什么一个函数(Array.prototype.includes())以检查是否一个元素在数组中发现,有关于输入的期望类型的知识。

我的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__/**"
    ]
  }

任何帮助,将不胜感激!

2个回答

是的,从技术上讲,允许searchElement参数 inArray<T>.includes()是 的超类型应该是安全T,但是标准的 TypeScript 库声明假定它只是T. 对于大多数目的,这是一个很好的假设,因为您通常不想像@GustavoLopes 提到的那样比较完全不相关的类型。但你的类型并非完全无关,是吗?

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


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

// 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'.

但最简单和仍然样-的-正确处理这种方式扩大的类型exampleArrstring[]

const stringArr: string[] = exampleArr; // no assertion
if (stringArr.includes(e.key)) {}  // okay

或者更简洁地说:

if ((exampleArr as string[]).includes(e.key)) {} // okay

扩大到string[]只是“某种”正确,因为 TypeScript 不安全地将inArray<T>视为协变T以方便起见。这很适合阅读,但是当您编写属性时,您会遇到问题:

(exampleArr as string[]).push("whoopsie"); // uh oh

但是由于您只是从数组中读取数据,因此非常安全。


Playground 链接到代码

感谢您的详尽解答!
2021-03-26 03:50:36
如果您不喜欢被迫强制转换数组以说服类型检查器您知道您在做什么,您可以对这个问题竖起大拇指,以表示对允许定义这些方法的机制感兴趣更精确地。
2021-04-01 03:50:36
优秀且解释清楚的答案!
2021-04-06 03:50:36
这是条件类型的一个非常好的用法。但是,如果您想缩小范围,则会引发错误,您必须改用this类型注释。在游乐场
2021-04-08 03:50:36

如果您要比较两种不同的类型,那么它们自然是不同的。

想象一下你有:

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. 它只知道它们有不同的类型,因此不应该比较它们。

“您必须将收到的字符串“转换”为 AllowedChars”👍
2021-04-12 03:50:36