javascript - Hvorfor må argumentet for Array.prototype.includes(searchElement) ha samme type som elementene i arrayet?
Jeg vet ærlig talt ikke om det er noe galt med innstillingene mine eller om dette er en TypeScript-funksjon. I det følgende eksemplet:
type AllowedChars = 'x' | 'y' | 'z';
const exampleArr: AllowedChars[] = ['x', 'y', 'z'];
function checkKey(e: KeyboardEvent) {
if (exampleArr.includes(e.key)) { // <-- here
// ...
}
}
Typeskript-kompilatoren klager over at Argument of type 'string' is not assignable to parameter of type 'AllowedChars'. Men hvor tilordner jeg? Array.prototype.includes returnerer en boolean (som jeg ikke lagrer). Jeg kunne undertrykke feilen med en typepåstand, slik som dette:
if (exampleArr.includes(e.key as AllowedChars)) {}
Men hvordan kan det være riktig, jeg forventer brukerinput som kan være hva som helst. Jeg forstår ikke hvorfor en funksjon (Array.prototype.includes()) laget for å sjekke om et element finnes i en array, skulle ha kunnskap om hvilken type input den kan forvente.
Min 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__/**"
]
}
All hjelp ville bli satt pris på!
Svar #1
Se microsoft/TypeScript#26255 for en full diskusjon om Array.prototype.includes() og overordnede typer.
Ja, teknisk sett burde det være trygt å tillate at searchElement-parameteren i Array<T>.includes() kan være en supertype av T, men standard TypeScript-bibliotekdeklarasjonen antar at det bare er T. For de fleste formål er dette en god antagelse, siden du vanligvis ikke vil sammenligne helt urelaterte typer, som @GustavoLopes nevner. Men typen din er vel ikke helt urelatert, er den?
Det finnes forskjellige måter å håndtere dette på. Påstanden du har kommet med er sannsynligvis den minst riktige fordi du påstår at en string er en AllowedChars selv om den kanskje ikke er det. Den «får jobben gjort», men du har rett i å føle deg ukomfortabel med det.
En annen måte er å lokalt overstyre standardbiblioteket via deklarasjonsfletting for å akseptere overordnede typer, noe som er litt komplisert fordi TypeScript ikke støtter overordnet typesbegrensning (se ms/TS#14520 for funksjonsforespørselen). I stedet bruker deklarasjonen betingede typer for å etterligne en overordnet typesbegrensning:
// 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;
}
}
Da vil den opprinnelige koden din bare fungere:
if (exampleArr.includes(e.key)) {} // okay
// call to includes inspects as
// (method) Array<AllowedChars>.includes<string>(
// searchElement: string, fromIndex?: number | undefined): boolean (+1 overload)
mens man fortsatt forhindrer sammenligning av helt urelaterte typer:
if (exampleArr.includes(123)) {} // error
// Argument of type '123' is not assignable to parameter of type 'AllowedChars'.
Men den enkleste og fortsatt riktige måten å håndtere dette på er å utvide typen til exampleArr til readonly string[]:
const stringArr: readonly string[] = exampleArr; // no assertion
if (stringArr.includes(e.key)) {} // okay
Eller mer kortfattet slik:
if ((exampleArr as readonly string[]).includes(e.key)) {} // okay
Å utvide til readonly string[] er greit, men vær forsiktig med å utvide til string[], som er litt farligere fordi TypeScript usikkert behandler Array<T> som kovariant i T for bekvemmelighet. Dette er greit for lesing, men når du skriver egenskaper støter du på problemer:
(exampleArr as string[]).push("oops"); // actually writing to an AllowedChars[]
Men siden du bare leser fra arrayet, er det helt trygt, og derfor anbefales readonly.
Lenke til kode på lekeplass
