Hvordan skrive en Typescript-array for å kun akseptere et spesifikt sett med verdier?


Jeg skriver en type-erklæringsfil for et bibliotek jeg ikke kontrollerer. En av metodene aksepterer et array av strenger som parameter, men disse strengene kan bare være veldig spesifikke verdier. Foreløpig skriver jeg denne parameteren som en string[], men jeg lurte på om det finnes en måte å forbedre dette for å inkludere de spesifikke verdiene også.

Eksempel kilde (jeg kan ikke endre dette):

Fruits(filter) {
    for (let fruit of filter.fruits)
    {
        switch(fruit)
        {
            case 'Apple':
                ...do stuff
            case 'Pear':
                ...do stuff
            default:
                console.error('Invalid Fruit');
                return false;
        }
    }
    return true;
}

Min nåværende typedeklarasjon:

function Fruits(filter: FruitFilter): boolean;

interface FruitFilter {
    fruits: string[];
}

Mens jeg skrev dette spørsmålet kom jeg opp med en delvis løsning ved å definere en unionstype av strengene som er gyldige, og deretter sette typen på feltet til et array av den unionen i stedet for et array av strenger. Dette gir meg kontrollen jeg ønsker, men jeg la merke til at hvis du skriver inn en ugyldig streng, merkes alle strengene i arrayet som ugyldige med feilmeldingen Type 'string' er not assignable to type 'Fruit'. Finnes det en bedre måte å gjøre dette på slik at bare den feilaktige strengen blir merket som ugyldig, eller er dette så nært jeg kommer?

Delvis løsning:

function Fruits(filter: FruitFilter): boolean;

type Fruit = 'Apple' | 'Pear'

interface FruitFilter {
    fruits: Fruit[];
}

Svar #1

Så, ditt problem ser ut til å være dette:

type Fruit = "Apple" | "Pear";
interface FruitFilter {
  fruits: Fruit[];
}
declare function Fruits(filter: FruitFilter): boolean;
Fruits({ fruits: ["Apple", "Apple", "Pear"] }); // okay
Fruits({ fruits: ["Apple", "Orange", "Pear"] }); // error
// actual error: ~~~~~~~  ~~~~~~~  ~~~~~~ <-- string not assignable to Fruit
// expected error:        ~~~~~~~ <-- "Orange" not assignable to Fruit

Det er ikke det at du har en feil, men at feilen ikke er ordentlig begrenset til de «dårlige» elementene i arrayet.

Mitt gjetning om hvorfor dette skjer er at kompilatoren har en tendens til å utvide strengliteraler til string og tuppeltyper til arrays med mindre du gir den hint om å ikke gjøre det. Derfor, når den ikke kan verifisere at fruits er av typen Fruit[], tar den et skritt tilbake og ser på hva du ga den. Den utvider ["Apple", "Orange", "Pear"] til string[] (glemmer både strengliteralene og det faktum at det er en tuppel med tre elementer), innser at string[] ikke kan tildeles Fruit[], og fortsetter deretter med å advare deg om dette ved å markere hvert element. Jeg gjorde et kort søk på GitHub-problemer for å se om dette noen gang har blitt rapportert, men jeg har ikke sett det. Det kan være verdt å sende inn en sak.

Uansett, for å teste gjetningen min bestemte jeg meg for å endre deklarasjonen av Fruits() for å antyde at vi ønsker en tuppel av strengliteraler hvis det overhodet er mulig. Merk at det for øyeblikket ikke finnes noen enkel måte å gjøre dette på; måtene å gi hint på akkurat nå er, eh, alkymistiske:

// 🧙⚗🌞🌛❓
declare function Fruits2<S extends string, T extends S[] | [S]>(arr: {
  fruits: T & { [K in keyof T]: Fruit };
}): boolean;
Fruits2({ fruits: ["Apple", "Apple", "Pear"] }); // okay
Fruits2({ fruits: ["Apple", "Orange", "Pear"] }); // error
//                          ~~~~~~~ <--string is not assignable to never

Vel, plasseringen av den feilen er der du vil ha den, selv om meldingen muligens fortsatt er forvirrende. Det er det som skjer når kompilatoren prøver å tilordne "Apple" til snittet Fruit & "Orange" som ikke eksisterer. Kompilatoren reduserer Fruit & "Orange" til never... korrekt, men muligens litt for tidlig for at feilmeldingen skal være nyttig.

Uansett, jeg anbefaler ikke denne "løsningen" siden den er mye mer komplisert og bare gir deg en litt bedre feilerfaring i feilsituasjoner. Men dette er i det minste noe som ligner på et svar på hvorfor det skjer, sammen med en mulig retning for hvordan man kan håndtere det (for eksempel finne eller rapportere et problem om det). Ok, lykke til!

Lenke til kode

Svar #2

Det fungerer også, hvis du ikke vil ha en type

export interface MyInterface {
   fruits: Array<'apple' | 'pear' | 'strawberry'>
}

Relaterte artikler