javascript - Typescript-type 'string' kan ikke tildeles til typen


Her er hva jeg har i fruit.ts

export type Fruit = "Orange" | "Apple" | "Banana"

Nå importerer jeg fruit.ts i en annen TypeScript-fil. Her er hva jeg har

myString:string = "Banana";

myFruit:Fruit = myString;

Når jeg gjør

myFruit = myString;

Jeg får en feil:

Type 'string' kan ikke tildeles typen '"Orange" | "Apple" | "Banana"'

Hvordan kan jeg tilordne en streng til en variabel av den tilpassede typen Fruit?

Svar #1

Oppdater

Som nevnt i @Simon_Weaver sitt svar, siden TypeScript versjon 3.4 er det mulig å erklære det som const:

let fruit = "Banana" as const;

Gammelt svar

Du må kaste det:

export type Fruit = "Orange" | "Apple" | "Banana";
let myString: string = "Banana";

let myFruit: Fruit = myString as Fruit;

Legg også merke til at når du bruker strengliteral, må du bruke bare én |

Svar #2

Typescript 3.4 introduserte den nye 'const' påstanden

Du kan nå forhindre at litterale typer (f.eks. 'orange' eller 'red') blir 'videt ut' til typen string med en såkalt const-påstand.

Du vil kunne gjøre:

let fruit = 'orange' as const;  // or...
let fruit = <const> 'orange';

Og så vil det ikke lenger bli til en string - som er roten til problemet i spørsmålet.

Du kan også gjøre det på et helt objekt:

let animals = [ { species: 'dog' }, { species: 'cat' } ] as const;

type firstAnimal = (typeof animals)[0]['species'];  // string literal 'dog'

Bonus Tips: Du kan også bruke <const> false eller <const> true for å representere en boolean som må være sann eller falsk. Dette kan være nyttig i diskriminerte unions noen ganger. Du vil vite det når du ser det.

Svar #3

Når du gjør dette:

export type Fruit = "Orange" | "Apple" | "Banana"

...du oppretter en type kalt Fruit som kun kan inneholde literalene "Orange", "Apple" og "Banana". Denne typen utvider String, derfor kan den tildeles til String. Derimot utvider ikke String "Orange" | "Apple" | "Banana", så den kan ikke tildeles til det. String er mindre spesifikk. Den kan være enhver streng.

Når du gjør dette:

export type Fruit = "Orange" | "Apple" | "Banana"

const myString = "Banana";

const myFruit: Fruit = myString;

...det fungerer. Hvorfor? Fordi den faktiske typen til myString i dette eksemplet er "Banana". Ja, "Banana" er typen. Den utvider String så den kan tildeles til String. I tillegg utvider en type en Union Type når den utvider en hvilken som helst av komponentene sine. I dette tilfellet utvider "Banana", typen, "Orange" | "Apple" | "Banana" fordi den utvider en av komponentene. Derfor kan "Banana" tildeles til "Orange" | "Apple" | "Banana" eller Fruit.

Svar #4

Det er flere situasjoner som vil gi deg denne spesifikke feilen. I tilfellet med OP var det en verdi definert eksplisitt som en streng. Så jeg må anta at dette kanskje kom fra en nedtrekksmeny, en webtjeneste eller en rå JSON-streng.

I så fall er en enkel casting <Fruit> fruitString eller fruitString as Fruit den eneste løsningen (se andre svar). Du vil aldri kunne forbedre dette på kompileringstid. [Rediger: Se mitt andre svar om <const>] !

Det er imidlertid veldig lett å støte på denne samme feilen når man bruker konstanter i koden sin som aldri er ment å være av typen streng. Svaret mitt fokuserer på det andre scenariet:


Først av alt: Hvorfor er «magiske» strengkonstanter ofte bedre enn en enum?

  • Jeg liker hvordan en strengkonstant ser ut sammenlignet med en enum - den er kompakt og 'javascripty'
  • Gir mer mening hvis komponenten du bruker allerede bruker strengkonstanter.
  • Å måtte importere en 'enum type' bare for å få en enumerasjonsverdi kan være problematisk i seg selv
  • Uansett hva jeg gjør, vil jeg at det skal være kompilering-sikkert, slik at hvis jeg legger til eller fjerner en gyldig verdi fra foreningstypen, eller skriver den feil, må det gi en kompileringfeil.

Heldigvis når du definerer:

export type FieldErrorType = 'none' | 'missing' | 'invalid'

...du definerer faktisk en union av typer hvor 'missing' faktisk er en type!

Jeg støter ofte på feilen 'ikke tildelbar' hvis jeg har en streng som 'banana' i TypeScript og kompilatoren tror jeg mente det som en streng, mens jeg egentlig ønsket at den skulle være av typen banana. Hvor smart kompilatoren kan være, vil avhenge av strukturen på koden din.

Her er et eksempel på når jeg fikk denne feilen i dag:

// this gives me the error 'string is not assignable to type FieldErrorType'
fieldErrors: [ { fieldName: 'number', error: 'invalid' } ]

Så snart jeg fant ut at 'invalid' eller 'banana' kunne være enten en type eller en streng, innså jeg at jeg bare kunne påstå at en streng er av den typen. I hovedsak kaste den til seg selv, og fortelle kompilatoren nei, jeg vil ikke at dette skal være en streng!

// so this gives no error, and I don't need to import the union type too
fieldErrors: [ { fieldName: 'number', error: <'invalid'> 'invalid' } ]

Så hva er galt med bare å «kaste» til FieldErrorType (eller Fruit)

// why not do this?
fieldErrors: [ { fieldName: 'number', error: <FieldErrorType> 'invalid' } ]

Det er ikke kompileringstidssikkert:

 <FieldErrorType> 'invalidddd';  // COMPILER ALLOWS THIS - NOT GOOD!
 <FieldErrorType> 'dog';         // COMPILER ALLOWS THIS - NOT GOOD!
 'dog' as FieldErrorType;        // COMPILER ALLOWS THIS - NOT GOOD!

Hvorfor? Dette er TypeScript, så <FieldErrorType> er en påstand, og du forteller kompilatoren at en hund er en FieldErrorType! Og kompilatoren vil tillate det!

MEN hvis du gjør følgende, vil kompilatoren da konvertere strengen til en type

 <'invalid'> 'invalid';     // THIS IS OK  - GOOD
 <'banana'> 'banana';       // THIS IS OK  - GOOD
 <'invalid'> 'invalidddd';  // ERROR       - GOOD
 <'dog'> 'dog';             // ERROR       - GOOD

Bare pass på for dumme skrivefeil som dette:

 <'banana'> 'banan';    // PROBABLY WILL BECOME RUNTIME ERROR - YOUR OWN FAULT!

En annen måte å løse problemet på er ved å caste foreldren objektet:

Mine definisjoner var som følger:

   export type FieldName = 'number' | 'expirationDate' | 'cvv';
   export type FieldError = 'none' | 'missing' | 'invalid';
   export type FieldErrorType = { field: FieldName, error: FieldError };

La oss si at vi får en feil med dette (feilen med at strengen ikke kan tilordnes):

  fieldErrors: [ { field: 'number', error: 'invalid' } ]

Vi kan 'assert' hele objektet som en FieldErrorType slik:

  fieldErrors: [ <FieldErrorType> { field: 'number', error: 'invalid' } ]

Da unngår vi å måtte gjøre <'invalid'> 'invalid'.

Men hva med skrivefeil? Gjør ikke <FieldErrorType> bare assert at det som er på høyre side er av den typen? Ikke i dette tilfellet - heldigvis vil kompilatoren KLAGE hvis du gjør dette, fordi den er smart nok til å vite at det er umulig:

  fieldErrors: [ <FieldErrorType> { field: 'number', error: 'dog' } ]

Svar #5

Jeg ser at dette er litt gammelt, men det kan være en bedre løsning her.

Når du vil ha en streng, men ønsker at strengen bare skal samsvare med visse verdier, kan du bruke enums.

For eksempel:

enum Fruit {
    Orange = "Orange",
    Apple  = "Apple",
    Banana = "Banana"
}

let myFruit: Fruit = Fruit.Banana;

Nå vil du vite at uansett hva, vil myFruit alltid være strengen "Banana" (eller hvilken som helst annen tellbar verdi du velger). Dette er nyttig for mange ting, enten det er å gruppere lignende verdier som dette, eller å kartlegge brukervennlige verdier til maskinvennlige verdier, samtidig som man håndhever og begrenser hvilke verdier kompilatoren vil tillate.

Relaterte artikler