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!
The JavaScript array prototype constructor is used to allow to add new methods and properties to the Array() object. If the method is constructed, then it will available for every array. When constructing a property, All arrays will be given the property, and its value, as default.
prototype allows you to add new properties and methods to arrays. prototype is a property available with all JavaScript objects.
Using includes() Method: If array contains an object/element can be determined by using includes() method. This method returns true if the array contains the object/element else return false. Example: html.
The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.
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 because TypeScript doesn't support supertype constraints (see ms/TS#14520 for the feature request). Instead, the declaration uses conditional types to emulate a supertype constraint:
// 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 correct way to deal with this is to widen the type of exampleArr
to readonly string[]
:
const stringArr: readonly string[] = exampleArr; // no assertion
if (stringArr.includes(e.key)) {} // okay
Or more succinctly like:
if ((exampleArr as readonly string[]).includes(e.key)) {} // okay
Widening to readonly string[]
is fine, but be careful widening to string[]
, which is a little more dangerous 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, don't do this
But since you're just reading from the array it's perfectly safe, and why readonly
is recommended.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With