Typu pisma maszynowego „ciąg” nie można przypisać do typu

161

Oto, co mam w fruit.ts

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

Teraz importuję fruit.ts w innym pliku maszynopisu. Oto co mam

myString:string = "Banana";

myFruit:Fruit = myString;

Kiedy robię

myFruit = myString;

Pojawia się błąd:

Nie można przypisać typu „string” do typu „Orange” | „Jabłko” | "Banan"'

Jak mogę przypisać ciąg do zmiennej typu niestandardowego Fruit?

user6123723
źródło
1
Czy na pewno używasz apostrofów i cudzysłowów w programie export type Fruit?
Weather Vane
1
@WeatherVane Właśnie sprawdziłem moje Fruit.ts. Mam tam pojedyncze cudzysłowy dla typu eksportu Fruit = „Orange” || „Jabłko” || „Banan”. Dziękuję
user6123723
Wciąż wygląda na podwójne cudzysłowy ...
Weather Vane

Odpowiedzi:

231

Musisz go przesłać :

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

let myFruit: Fruit = myString as Fruit;

Zauważ również, że używając literałów łańcuchowych , musisz użyć tylko jednego|

Edytować

Jak wspomniano w drugiej odpowiedzi @Simon_Weaver, teraz można to potwierdzić const:

let fruit = "Banana" as const;
Nitzan Tomer
źródło
11
niezły :) w większości przypadków const myFruit: Fruit = "Banana"wystarczy.
Jacka
Czy mogę zrobić to odwrotnie? Mam na myśli coś takiego. let myFruit:Fruit = "Apple" let something:string = myFruit as string To daje mi błąd: Konwersja typu „Fruit” na typ „string” może być błędem.
Siraj Alam,
@Siraj Powinno działać dobrze, nawet nie potrzebujesz as stringczęści. Wypróbowałem twój kod na placu zabaw i nie ma błędów.
Nitzan Tomer
@NitzanTomer stackoverflow.com/questions/53813188/ ... Proszę spojrzeć na moje szczegółowe pytanie
Siraj Alam
Ale teraz, jeśli popełnię literówkę const myString: string = 'Bananaaa'; , nie otrzymuję błędów kompilacji z powodu rzutowania ... czy nie ma sposobu, aby to zrobić podczas sprawdzania ciągu?
blub
66

Typescript 3.4wprowadza nową asercję „const”

Możesz teraz zapobiec „rozszerzaniu” typów dosłownych (np. 'orange'Lub 'red') w celu wpisania stringza pomocą tak zwanego constpotwierdzenia.

Będziesz mógł:

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

I wtedy już się nie zmieni string- co jest źródłem problemu w pytaniu.

Simon_Weaver
źródło
Dla osób, które tak jak ja nie mają jeszcze wersji 3.4. <const> można zastąpić dowolnym, ale oczywiście nie jest tak czysty jak to rozwiązanie.
Pieter De Bie
2
Preferowana składnia byłaby let fruit = 'orange' as const;następująca przy przestrzeganiu reguły asercji typu bez kątownika
ThunderDev,
2
To jest poprawna odpowiedź dla współczesnego maszynopisu. Zapobiega niepotrzebnemu importowi typów i umożliwia poprawne pisanie strukturalne.
James McMahon
24

Kiedy to robisz:

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

... tworzysz nazwany typ, Fruitktóry może zawierać tylko literały "Orange", "Apple"i "Banana". Ten typ rozszerza się String, dlatego można go przypisać String. Jednak StringNIE rozszerza się "Orange" | "Apple" | "Banana", więc nie można go do niego przypisać. Stringjest mniej szczegółowy . Może to być dowolny ciąg .

Kiedy to robisz:

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

const myString = "Banana";

const myFruit: Fruit = myString;

...to działa. Czemu? Ponieważ rzeczywisty typ od myStringw tym przykładzie "Banana". Tak, "Banana"to jest typ . Jest rozszerzany, Stringwięc można go przypisać do String. Ponadto typ rozszerza Union Type, gdy rozszerza którykolwiek z jego składników. W tym przypadku "Banana"typ rozszerza się, "Orange" | "Apple" | "Banana"ponieważ przedłuża jeden z jego składników. Dlatego "Banana"można go przypisać do "Orange" | "Apple" | "Banana"lub Fruit.

André Pena
źródło
2
To zabawne, że możesz włożyć, <'Banana'> 'Banana'a to „rzuci” "Banana"strunę "Banana"na typ !!!
Simon_Weaver,
2
Ale teraz możesz zrobić, <const> 'Banana'co jest lepsze :-)
Simon_Weaver
11

Widzę, że jest to trochę stare, ale tutaj może być lepsze rozwiązanie.

Jeśli chcesz, aby ciąg znaków był zgodny tylko z określonymi wartościami, możesz użyć wyliczeń .

Na przykład:

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

let myFruit: Fruit = Fruit.Banana;

Teraz wiesz, że bez względu na wszystko, myFruit zawsze będzie ciągiem „Banan” (lub jakąkolwiek inną wyliczalną wartością, którą wybierzesz). Jest to przydatne w wielu przypadkach, niezależnie od tego, czy chodzi o grupowanie podobnych wartości, jak to, czy mapowanie przyjaznych dla użytkownika wartości na wartości przyjazne dla maszyny, a wszystko to przy wymuszaniu i ograniczaniu wartości, na które zezwala kompilator.

Steve Adams
źródło
1
To zabawne, ponieważ mam ten problem, kiedy próbuję uciec od tego. Coraz bardziej doprowadza mnie to do szału. Próbuję być
``
1
To również powoduje odwrotny problem. Nie możesz już tego robić let myFruit: Fruit = "Banana".
Sean Burton
11

Jest kilka sytuacji, które spowodują ten konkretny błąd. W przypadku PO była to wartość zdefiniowana jawnie jako ciąg . Muszę więc założyć, że może to pochodzi z listy rozwijanej, usługi internetowej lub surowego ciągu JSON.

W takim przypadku zwykła obsada <Fruit> fruitStringlub fruitString as Fruitjest jedynym rozwiązaniem (zobacz inne odpowiedzi). Nigdy nie byłbyś w stanie tego ulepszyć w czasie kompilacji. [ Edytuj: Zobacz moją drugą odpowiedź na temat<const> ]!

Jednak bardzo łatwo jest napotkać ten sam błąd, używając stałych w kodzie, które nigdy nie mają być typu string . Moja odpowiedź skupia się na tym drugim scenariuszu:


Po pierwsze: dlaczego „magiczne” stałe łańcuchowe są często lepsze niż wyliczenie?

  • Podoba mi się sposób, w jaki wygląda stała łańcuchowa w porównaniu z wyliczeniem - jest kompaktowy i „javascripty”
  • Ma to większy sens, jeśli składnik, którego używasz, już używa stałych łańcuchowych.
  • Konieczność zaimportowania „typu wyliczenia” tylko po to, aby uzyskać wartość wyliczenia, może być sama w sobie kłopotliwa
  • Cokolwiek robię, chcę, aby kompilacja była bezpieczna, więc jeśli dodam, usunę prawidłową wartość z typu unii lub błędnie ją wpiszę, MUSI to dać błąd kompilacji.

Na szczęście, kiedy zdefiniujesz:

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

... faktycznie definiujesz sumę typów, gdzie w 'missing'rzeczywistości jest typem!

Często napotykam błąd „nie można przypisać”, jeśli mam ciąg taki jak 'banana'w moim maszynopisie, a kompilator uważa , że mam na myśli ciąg, podczas gdy naprawdę chciałem, aby był typu banana. To, jak inteligentny może być kompilator, będzie zależeć od struktury twojego kodu.

Oto przykład, kiedy dzisiaj otrzymałem ten błąd:

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

Jak tylko dowiedziałem się, że 'invalid'albo 'banana'może być albo typu lub ciągiem zdałem sobie sprawę, mogłem tylko dochodzić do tego typu ciąg . Zasadniczo rzuć to na siebie i powiedz kompilatorowi nie, nie chcę, aby to był ciąg !

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

Więc co jest złego w „przesyłaniu” do FieldErrorType(lub Fruit)

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

Czas kompilacji nie jest bezpieczny:

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

Czemu? To jest maszynopis, więc <FieldErrorType>jest to asercja i mówisz kompilatorowi, że pies to FieldErrorType ! Kompilator na to pozwoli!

ALE jeśli wykonasz następujące czynności, kompilator przekonwertuje ciąg na typ

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

Uważaj tylko na takie głupie literówki:

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

Innym sposobem rozwiązania problemu jest rzutowanie obiektu nadrzędnego:

Moje definicje brzmiały następująco:

typ eksportu FieldName = 'number' | „expirationDate” | „cvv”; typ eksportu FieldError = 'none' | „brakujący” | 'nieważny'; typ eksportu FieldErrorType = {field: FieldName, błąd: FieldError};

Powiedzmy, że otrzymujemy błąd z tym (błąd nie do przypisania ciągu):

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

Możemy `` potwierdzić '' cały obiekt w następujący sposób FieldErrorType:

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

Wtedy nie musimy tego robić <'invalid'> 'invalid'.

Ale co z literówkami? Nie <FieldErrorType>tylko twierdzi, że ma prawo być tego typu. Nie w tym przypadku - na szczęście kompilator będzie narzekał, jeśli to zrobisz, ponieważ jest na tyle sprytny, aby wiedzieć, że to niemożliwe:

  fieldErrors: [ <FieldErrorType> { field: 'number', error: 'dog' } ]
Simon_Weaver
źródło
W trybie ścisłym mogą występować subtelności. Zaktualizuje odpowiedź po potwierdzeniu.
Simon_Weaver
1

Wszystkie powyższe odpowiedzi są prawidłowe, jednak w niektórych przypadkach typ literału ciągu jest częścią innego złożonego typu. Rozważmy następujący przykład:

  // in foo.ts
  export type ToolbarTheme = {
    size: 'large' | 'small',
  };

  // in bar.ts
  import { ToolbarTheme } from './foo.ts';
  function useToolbarTheme(theme: ToolbarTheme) {/* ... */}

  // Here you will get the following error: 
  // Type 'string' is not assignable to type '"small" | "large"'.ts(2322)
  ['large', 'small'].forEach(size => (
    useToolbarTheme({ size })
  ));

Masz wiele rozwiązań, aby to naprawić. Każde rozwiązanie jest ważne i ma swoje własne przypadki użycia.

1) Pierwszym rozwiązaniem jest zdefiniowanie typu rozmiaru i wyeksportowanie go z pliku foo.ts. Jest to dobre, jeśli musisz samodzielnie pracować z parametrem rozmiaru. Na przykład masz funkcję, która akceptuje lub zwraca parametr typu size i chcesz go wpisać.

  // in foo.ts
  export type ToolbarThemeSize = 'large' | 'small';
  export type ToolbarTheme = {
    size: ToolbarThemeSize
  };

  // in bar.ts
  import { ToolbarTheme, ToolbarThemeSize } from './foo.ts';
  function useToolbarTheme(theme: ToolbarTheme) {/* ... */}
  function getToolbarSize(): ToolbarThemeSize  {/* ... */}

  ['large', 'small'].forEach(size => (
    useToolbarTheme({ size: size as ToolbarThemeSize })
  ));

2) Drugą opcją jest po prostu rzutowanie go na typ ToolbarTheme. W takim przypadku nie musisz ujawniać wewnętrznej części ToolbarTheme, jeśli nie potrzebujesz.

  // in foo.ts
  export type ToolbarTheme = {
    size: 'large' | 'small'
  };

  // in bar.ts
  import { ToolbarTheme } from './foo.ts';
  function useToolbarTheme(theme: ToolbarTheme) {/* ... */}

  ['large', 'small'].forEach(size => (
    useToolbarTheme({ size } as ToolbarTheme)
  ));
Saman Shafigh
źródło
0

Jeśli dropdownvalue[]na przykład rzutujesz na a podczas mockowania danych, utwórz go jako tablicę obiektów z właściwościami value i wyświetlania.

przykład :

[{'value': 'test1', 'display1': 'test display'},{'value': 'test2', 'display': 'test display2'},]
meol
źródło
0

Miałem ten sam problem, wprowadziłem poniżej zmiany i problem został rozwiązany.

Otwórz plik watchQueryOptions.d.ts

\apollo-client\core\watchQueryOptions.d.ts

Zmień typ zapytania na dowolny zamiast DocumentNode , taki sam dla mutacji

Przed:

export interface QueryBaseOptions<TVariables = OperationVariables> {
    query: **DocumentNode**;

Po:

export interface QueryBaseOptions<TVariables = OperationVariables> {
    query: **any**;
Anand N
źródło