javascript - Typescript Tipul 'string' nu poate fi asignat tipului


Iată ce am în fruit.ts

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

Acum import fruct.ts într-un alt fișier TypeScript. Iată ce am

myString:string = "Banana";

myFruit:Fruit = myString;

Când fac

myFruit = myString;

Primeasc o eroare:

Tipul 'string' nu poate fi asignat tipului '"Orange" | "Apple" | "Banana"'

Cum pot să atribui un șir unei variabile de tip personalizat Fruit?

Răspuns #1

Actualizare

Așa cum s-a menționat în răspunsul lui @Simon_Weaver, începând cu versiunea 3.4 a TypeScript, este posibil să se afirme ca const:

let fruit = "Banana" as const;

Răspuns vechi

Va trebui să îl arunci:

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

let myFruit: Fruit = myString as Fruit;

De asemenea, observați că atunci când folosiți literali de șiruri, trebuie să folosiți doar un singur |

Răspunsul #2

Typescript 3.4 a introdus noua afirmație 'const'

Acum puteți preveni ca tipurile literale (de ex. 'orange' sau 'red') să fie „lărgite” la tipul string printr-o așa-numită afirmație const.

Vei putea să faci:

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

Și atunci nu se va mai transforma singur într-un string - ceea ce este rădăcina problemei în întrebare.

Poți să faci asta și pe un obiect întreg:

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

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

Sfat Bonus: De asemenea, poți folosi <const> false sau <const> true pentru a reprezenta un boolean care trebuie să fie adevărat sau fals. Acest lucru poate fi util uneori în uniuni discriminate. Vei ști când să-l folosești.

Răspunsul #3

Când faci asta:

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

...creezi un tip numit Fruit care poate conține doar literalele "Orange", "Apple" și "Banana". Acest tip extinde String, prin urmare poate fi atribuit la String. Totuși, String NU extinde "Orange" | "Apple" | "Banana", deci nu îi poate fi atribuit. String este mai puțin specific. Poate fi orice șir de caractere.

Când faci asta:

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

const myString = "Banana";

const myFruit: Fruit = myString;

... Funcționează. De ce? Pentru că tipul real al myString din acest exemplu este "Banana". Da, "Banană" este genul ăsta. Extinde String, deci poate fi atribuit la String. În plus, un tip extinde un tip Union atunci când extinde oricare dintre componentele sale. În acest caz, "Banana", tipul, extinde "Orange" | "Apple" | "Banana" deoarece extinde una dintre componentele sale. Astfel, "Banană" poate fi atribuită la "Portocală" | "Măr" | "Banană" sau Fruct.

Răspunsul #4

Există mai multe situații care îți vor da această eroare particulară. În cazul OP, exista o valoare definită explicit ca șir de caractere. Așa că trebuie să presupun că poate aceasta a venit dintr-un meniu derulant, un serviciu web sau un șir JSON brut.

În acest caz, o simplă conversie <Fruit> fruitString sau fruitString as Fruit este singura soluție (vezi celelalte răspunsuri). Nu ai putea niciodată să îmbunătățești asta la momentul compilării. [Editare: Vezi și celălalt răspuns al meu despre <const>] !

Totuși, este foarte ușor să dai peste aceeași eroare atunci când folosești constante în codul tău care nu sunt niciodată menite să fie de tip string. Răspunsul meu se concentrează pe acel al doilea scenariu:


În primul rând: De ce sunt constantele de tip 'magic' string adesea mai bune decât un enum?

  • Îmi place cum arată o constantă de tip șir comparativ cu un enum - este compactă și 'javascriptică'
  • Are mai mult sens dacă componenta pe care o folosești deja folosește constante de tip șir.
  • A fi nevoit să imporți un 'tip enum' doar pentru a obține o valoare de enumerare poate fi problematic în sine
  • Orice fac vreau să fie sigur la compilare, astfel încât dacă adaug sau elimin o valoare validă din tipul uniunii, sau o scriu greșit, atunci TREBUIE să dea o eroare la compilare.

Din fericire, atunci când definești:

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

...de fapt definești o uniune de tipuri unde 'missing' este de fapt un tip!

Întâlnesc adesea eroarea „nu se poate atribui” dacă am un șir de caractere precum 'banana' în TypeScript și compilatorul crede că mă refeream la acesta ca la un șir, în timp ce eu de fapt doream să fie de tip banana. Cât de inteligent poate fi compilatorul va depinde de structura codului tău.

Iată un exemplu de când am primit această eroare astăzi:

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

De îndată ce am aflat că 'invalid' sau 'banana' ar putea fi fie un tip, fie un șir de caractere, am realizat că pot pur și simplu să afirm un șir de caractere în acel tip. Practic îl convertez la el însuși și îi spun compilatorului nu, nu vreau ca asta să fie un șir de caractere!

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

Deci ce e rău în a 'face cast' la FieldErrorType (sau Fruit)

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

Nu este sigur la timpul de compilare:

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

De ce? Acesta este TypeScript, așa că <FieldErrorType> este o afirmație și spui compilatorului că un câine este un FieldErrorType! Și compilatorul îl va permite!

DAR dacă faci următoarele, atunci compilatorul va converti șirul într-un tip

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

Doar ai grijă la greșeli stupide de tipul acesta:

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

O altă modalitate de a rezolva problema este prin transformarea obiectului părinte:

Definițiile mele au fost următoarele:

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

Să spunem că primim o eroare cu asta (eroarea de șir neasignabil):

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

Putem să 'afirmăm' întregul obiect ca un FieldErrorType astfel:

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

Atunci evităm să trebuie să facem <'invalid'> 'invalid'.

Dar ce te faci cu greșelile de tastare? Nu face <FieldErrorType> doar assert ca ceea ce se află pe dreapta să fie de acel tip? Nu în acest caz - din fericire, compilatorul VA reclama dacă faci asta, pentru că este suficient de inteligent să știe că este imposibil:

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

Răspunsul #5

Văd că acesta este puțin vechi, dar ar putea exista o soluție mai bună aici.

Când vrei un șir, dar vrei ca șirul să corespundă doar anumitor valori, poți folosi enumuri.

De exemplu:

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

let myFruit: Fruit = Fruit.Banana;

Acum vei ști că, indiferent de situație, myFruit va fi întotdeauna șirul „Banana” (sau orice altă valoare enumerabilă pe care o alegi). Acest lucru este util pentru multe lucruri, fie că este vorba de gruparea valorilor similare în acest fel, sau de maparea valorilor prietenoase pentru utilizator la valori prietenoase pentru mașină, totul în timp ce se aplică și se restricționează valorile pe care compilatorul le va permite.