Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
146 views
in Technique[技术] by (71.8m points)

javascript - Why does the argument for Array.prototype.includes(searchElement) need the same type as array elements?

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!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...