Typescript Тип 'string' не можна присвоювати типу


161

Ось що у мене є у fru.ts

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

Зараз я імпортую файл fru.ts в інший файл машинопису. Ось що я маю

myString:string = "Banana";

myFruit:Fruit = myString;

Коли я це роблю

myFruit = myString;

Я отримую помилку:

Тип 'рядок' не призначається типу "помаранчевий" | "Яблуко" | "Банан"

Як я можу призначити рядок змінній користувальницького типу Fruit?


1
Ви впевнені у використанні одиничних та подвійних лапок у export type Fruit?
Погода Ване

1
@WeatherVane Щойно перевірив мій Fruit.ts. У мене є єдині котирування на експорт типу Fruit = 'Orange' || 'Яблуко' || 'банан'. Дякую
користувач6123723

Досі виглядає як подвійні цитати для мене ...
Weather Vane

Відповіді:


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 Це дає мені помилку: перетворення типу 'фрукт' у тип 'рядок' може бути помилкою.
Сірай Алам

@Siraj Це повинно працювати чудово, as stringчастина вам навіть не потрібна . Я спробував ваш код на дитячій площадці, і помилок немає.
Нітзан Томер

@NitzanTomer stackoverflow.com/questions/53813188 / ... Будь ласка , подивіться на мій детальний питання
Сирай Алам

Але тепер, якщо я вводячи помилку, const myString: string = 'Bananaaa'; я не отримую помилок компіляції через кастинг ... чи немає способу це зробити під час введення перевірки рядка?
блю

66

Typescript 3.4вводить нове твердження "const"

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

Ви зможете:

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

І тоді вона вже не перетвориться на stringбільше - що є коренем проблеми у питанні.


Для людей, як і я, ще немає 3,4. <const> можна замінити будь-яким, але, звичайно, не такий чистий, як це рішення.
Пітер Де Бі

2
Переважним буде синтаксис, let fruit = 'orange' as const;коли слід дотримуватись правила затвердження типу no-angle-скоба
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. Крім того, тип розширює тип Union, коли він розширює будь-який з його компонентів. У цьому випадку "Banana"тип, поширюється, "Orange" | "Apple" | "Banana"оскільки розширює один із його компонентів. Отже, "Banana"присвоюється "Orange" | "Apple" | "Banana"або Fruit.


2
Це смішно, що ви можете насправді поставити, <'Banana'> 'Banana'і це буде "кидати" "Banana"рядок "Banana"типу !!!
Simon_Weaver

2
Але тепер ви можете зробити <const> 'Banana'що краще :-)
Simon_Weaver

11

Я бачу, це трохи старе, але тут може бути краще рішення.

Коли ви хочете, щоб рядок, але ви хочете, щоб рядок відповідав лише певним значенням, ви можете використовувати enums .

Наприклад:

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

let myFruit: Fruit = Fruit.Banana;

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


1
Це смішно, бо я отримую це питання, коли намагаюся втекти від цього. Це все частіше мене заганяє. Я намагаюся бути "javascripty" і використовую магічні рядки, обмежені типом (замість перерахунку), і, здається, все більше і більше
буває зворотне ввімкнення

1
Це також викликає протилежну проблему. Більше не можна робити let myFruit: Fruit = "Banana".
Шон Бертон

11

Є кілька ситуацій, які дадуть вам саме цю помилку. У випадку з ОП було значення, визначене явно як рядок . Тож я маю припустити, що, можливо, це сталося із спадного меню, веб-сервісу чи необробленої рядки JSON.

У цьому випадку простий склад <Fruit> fruitStringабо fruitString as Fruitєдине рішення (див. Інші відповіді). Ви ніколи не зможете покращити це під час компіляції. [ Редагувати: див. Мою іншу відповідь про<const> ]!

Однак зіткнутися з цією самою помилкою дуже просто при використанні констант у вашому коді, які ніколи не повинні мати тип рядка . Моя відповідь зосереджена на тому другому сценарії:


Перш за все: чому "магічні" струнні константи часто кращі за перерахунок?

  • Мені подобається, як струнна константа виглядає проти перерахунку - вона компактна і "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 = 'число' | 'дата закінчення' | 'cvv'; тип експорту FieldError = 'немає' | 'відсутній' | 'недійсний'; тип експорту FieldErrorType = {поле: 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

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) Другий варіант - просто передати його типу TypebarTheme. У цьому випадку вам не потрібно розкривати внутрішню панель інструментів, якщо вона вам не потрібна.

  // 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[]час макетування даних передаваєте на a , складіть його як масив об'єктів зі значеннями та властивостями відображення.

приклад :

[{'value': 'test1', 'display1': 'test display'},{'value': 'test2', 'display': 'test display2'},]

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**;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.