Typescript Тип 'string' нельзя назначить типу

162

Вот что у меня в fruit.ts

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

Сейчас я импортирую fruit.ts в другой машинописный файл. Вот что у меня есть

myString:string = "Banana";

myFruit:Fruit = myString;

Когда я делаю

myFruit = myString;

Я получаю ошибку:

Тип 'string' нельзя назначить типу "" Orange "| "Яблоко" | "Банан"'

Как я могу присвоить строку переменной пользовательского типа Fruit?

user6123723
источник
1
Вы абсолютно уверены в использовании одинарных и двойных кавычек export type Fruit?
Флюгер
1
@WeatherVane Только что проверил мои Fruit.ts. У меня есть одинарные кавычки для типа экспорта Fruit = 'Orange' || «Яблоко» || «Банан». Спасибо
user6123723
Все еще выглядит как двойные кавычки для меня ...
Флюгер

Ответы:

231

Вам нужно будет разыграть это :

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

let myFruit: Fruit = myString as Fruit;

Также обратите внимание, что при использовании строковых литералов вам нужно использовать только один|

редактировать

Как упоминалось в другом ответе @Simon_Weaver, теперь можно утверждать, что const:

let fruit = "Banana" as const;
Ницан Томер
источник
11
хороший :) в большинстве случаев const myFruit: Fruit = "Banana"подойдет.
Jacka
Могу ли я сделать его наоборот? Я имею в виду что-то вроде этого. let myFruit:Fruit = "Apple" let something:string = myFruit as string Это дает мне ошибку: преобразование типа 'Fruit' в тип 'string' может быть ошибкой.
Сирадж Алам
@ Сирадж Это должно работать просто отлично, тебе даже не нужна as stringроль. Я попробовал ваш код на детской площадке, и там нет ошибок.
Ницан Томер
@NitzanTomer stackoverflow.com/questions/53813188/… Пожалуйста, посмотрите на мой подробный вопрос
Сирадж Алам
Но теперь, если я опечатал, const myString: string = 'Bananaaa'; я не получаю ошибки компиляции из-за приведения ... нет ли способа сделать это во время проверки типа строки?
рыдать
67

Typescript 3.4вводит новое утверждение const

Теперь вы можете предотвратить «расширение» литеральных типов (например, 'orange'или 'red') stringс помощью так называемого constутверждения.

Вы сможете сделать:

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

И тогда он больше не превратится в себя string- что является корнем проблемы в этом вопросе.

Simon_Weaver
источник
Для людей, которые, как я, еще не на 3.4. <const> может быть заменен любым, но, конечно, не так чист, как это решение.
Питер Де Би
2
Предпочтительный синтаксис будет let fruit = 'orange' as const;при соблюдении правила утверждения типа без угловых скобок
ThunderDev
2
Это правильный ответ для современного Typescript. Это предотвращает ненужный импорт типов и позволяет структурной типизации делать свое дело правильно.
Джеймс МакМэхон
24

Когда вы делаете это:

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

... вы создаете тип с именем, Fruitкоторый может содержать только литералы "Orange", "Apple"и "Banana". Этот тип расширяется String, следовательно, он может быть назначен String. Однако StringНЕ расширяется "Orange" | "Apple" | "Banana", поэтому его нельзя назначить. Stringявляется менее определенным . Это может быть любая строка .

Когда вы делаете это:

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

const myString = "Banana";

const myFruit: Fruit = myString;

...оно работает. Зачем? Поскольку фактический тип из myStringв этом примере "Banana". Да, "Banana"это тип . Он расширяется, Stringпоэтому его можно назначить String. Кроме того, тип расширяет тип объединения, когда он расширяет любой из его компонентов. В этом случае "Banana"тип расширяется, "Orange" | "Apple" | "Banana"поскольку расширяет один из его компонентов. Следовательно, "Banana"присваивается "Orange" | "Apple" | "Banana"или Fruit.

Андре Пена
источник
2
Забавно, что вы можете на самом деле поставить, <'Banana'> 'Banana'и это «приведёт» "Banana"строку к "Banana"типу !!!
Simon_Weaver
2
Но теперь вы можете делать то, <const> 'Banana'что лучше :-)
Simon_Weaver
11

Я вижу, что это немного старый, но здесь может быть лучшее решение.

Если вам нужна строка, но вы хотите, чтобы строка соответствовала только определенным значениям, вы можете использовать перечисления .

Например:

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

let myFruit: Fruit = Fruit.Banana;

Теперь вы будете знать, что несмотря ни на что, myFruit всегда будет строкой "Banana" (или любым другим перечисляемым значением, которое вы выберете). Это полезно для многих вещей, будь то группировка похожих значений, подобных этой, или отображение удобных для пользователя значений в удобные для машины значения, все это при одновременном применении и ограничении значений, разрешенных компилятором.

Стив Адамс
источник
1
Забавно, потому что я получаю эту проблему, пытаясь уйти от этого. Это все больше сводит меня с ума. Я пытаюсь быть 'javascripty' и использую магические строки, ограниченные типом (вместо перечисления), и это, кажется,
вызывает у
1
Это также вызывает противоположную проблему. Вы больше не можете сделать let myFruit: Fruit = "Banana".
Шон Бертон
11

Есть несколько ситуаций, которые приведут вас к этой конкретной ошибке. В случае OP было значение, определенное явно как строка . Поэтому я должен предположить, что, возможно, это произошло из-за раскрывающегося списка, веб-службы или необработанной строки JSON.

В этом случае простое приведение <Fruit> fruitStringили fruitString as Fruitединственное решение (см. Другие ответы). Вы никогда не сможете улучшить это во время компиляции. [ Редактировать: Смотрите мой другой ответ о<const> ]!

Тем не менее, очень легко столкнуться с этой же ошибкой при использовании констант в вашем коде, которые никогда не предназначались для типа string . Мой ответ сосредоточен на втором сценарии:


Прежде всего: почему «магические» строковые константы часто лучше, чем перечисление?

  • Мне нравится, как выглядит строковая константа по сравнению с enum - она ​​компактна и «javascripty»
  • Имеет больше смысла, если используемый вами компонент уже использует строковые константы.
  • Необходимость импортировать 'enum type' только для получения значения перечисления может быть проблематичной сама по себе
  • Что бы я ни делал, я хочу, чтобы он был безопасным для компиляции, поэтому, если я добавлю удалить допустимое значение из типа объединения или неправильно наберет его, тогда он ДОЛЖЕН вызвать ошибку компиляции.

К счастью, когда вы определите:

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

... вы на самом деле определяете объединение типов, где 'missing'на самом деле есть тип!

Я часто сталкиваюсь с ошибкой «не присваивается», если у меня есть строка, как 'banana'в моем машинописном тексте, и компилятор думает, что я имел в виду это как строку, тогда как я действительно хотел, чтобы она имела типbanana . Насколько умным может быть компилятор, будет зависеть от структуры вашего кода.

Вот пример того, когда я получил эту ошибку сегодня:

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

Как только я узнал, что 'invalid'и 'banana'может быть либо типа или строки , я понял , что я мог бы просто утверждать строку в этот тип . По сути, приведите его к себе и скажите компилятору, что я не хочу, чтобы это была строка !

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

Так что не так с просто «приведение» к FieldErrorType(или Fruit)

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

Это не безопасное время компиляции:

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

Зачем? Это машинопись, так <FieldErrorType>что это утверждение, и вы говорите компилятору, что собака является FieldErrorType ! И компилятор это позволит!

НО, если вы сделаете следующее, то компилятор преобразует строку в тип

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

Просто следите за глупыми опечатками, такими как это:

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

Другой способ решения проблемы - приведение родительского объекта:

Мои определения были следующими:

тип экспорта FieldName = 'число' | 'expirationDate' | 'CVV'; тип экспорта FieldError = 'none' | «отсутствует» | 'недействительным'; тип экспорта FieldErrorType = {field: FieldName, ошибка: FieldError};

Допустим, мы получили ошибку с этим (строка не присваиваемая ошибка):

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

Мы можем «утверждать» весь объект FieldErrorTypeследующим образом:

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

Тогда мы избегаем необходимости делать <'invalid'> 'invalid'.

Но как насчет опечаток? Не <FieldErrorType>просто утверждать, что имеет право быть такого типа. Не в этом случае - к счастью, компилятор будет жаловаться, если вы сделаете это, потому что он достаточно умен, чтобы знать, что это невозможно:

  fieldErrors: [ <FieldErrorType> { field: 'number', error: 'dog' } ]
Simon_Weaver
источник
Могут быть тонкости со строгим режимом. Обновлю ответ после подтверждения.
Simon_Weaver
1

Все вышеприведенные ответы верны, однако в некоторых случаях строковый литеральный тип является частью другого сложного типа. Рассмотрим следующий пример:

  // 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 })
  ));

У вас есть несколько решений, чтобы это исправить. Каждое решение является действительным и имеет свои варианты использования.

1) Первое решение - определить тип для размера и экспортировать его из foo.ts. Это хорошо, если вам нужно работать с параметром размера самостоятельно. Например, у вас есть функция, которая принимает или возвращает параметр размера шрифта, и вы хотите его ввести.

  // 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) Второй вариант - просто привести его к типу ToolbarTheme. В этом случае вам не нужно открывать внутреннюю часть ToolbarTheme, если вам это не нужно.

  // 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)
  ));
Саман Шафиг
источник
0

Если вы приводите, dropdownvalue[]к примеру, при манипулировании данными, составьте их как массив объектов со свойствами value и display.

пример :

[{'value': 'test1', 'display1': 'test display'},{'value': 'test2', 'display': 'test display2'},]
meol
источник
0

Я столкнулся с той же проблемой, я внес следующие изменения, и проблема была решена.

Открыть файл watchQueryOptions.d.ts

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

Изменить тип запроса любой вместо DocumentNode , То же самое для мутации

Перед:

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

После:

export interface QueryBaseOptions<TVariables = OperationVariables> {
    query: **any**;
Ананд Н
источник