Typescript类型'不能赋值给类型

这是我的水果。ts

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

现在我在进口水果。Ts在另一个typescript文件中。这是我有的

myString:string = "Banana";


myFruit:Fruit = myString;

当我这样做的时候

myFruit = myString;

我得到一个错误:

类型“字符串”不能分配给类型““橙色”|“苹果”| “香蕉”< / p >

如何将字符串分配给自定义类型水果的变量?

600392 次浏览

更新

正如@Simon_Weaver的回答中提到的,自TypeScript 3.4版本以来,可以将它断言为const:

let fruit = "Banana" as const;

旧的答案

你需要:

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


let myFruit: Fruit = myString as Fruit;

还要注意,在使用字符串字面值时,只需要使用一个|

我知道这有点过时了,但这里可能有更好的解决方案。

当你想要一个字符串,但你想要字符串只匹配某些值时,你可以使用枚举

例如:

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


let myFruit: Fruit = Fruit.Banana;

现在您将知道,无论如何,myFruit将始终是字符串“Banana”(或您选择的任何其他可枚举值)。这对很多事情都很有用,无论是将类似的值分组,还是将用户友好的值映射到机器友好的值,同时强制和限制编译器允许的值。

当你这样做的时候:

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

...你正在创建一个名为Fruit的类型,它只能包含字面量"Orange""Apple""Banana"。这种类型扩展了String,因此它可以被赋值给String。然而,String并没有扩展"Orange" | "Apple" | "Banana",所以它不能被赋值给"Orange" | "Apple" | "Banana"String不特定的。它可以是"Orange"0。

当你这样做的时候:

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


const myString = "Banana";


const myFruit: Fruit = myString;

...它的工作原理。为什么?因为本例中myString的实际"Banana"0是"Banana"。是的,"Banana""Banana"0。它扩展了String,所以它可以赋值给String。此外,当类型"Banana"2扩展其组件的"Banana"3时,它就是联合类型。在这种情况下,类型"Banana"扩展了"Orange" | "Apple" | "Banana",因为它扩展了它的一个组件。因此,"Banana"可赋值给"Orange" | "Apple" | "Banana"Fruit

有几种情况会给出这种特定的错误。在OP的情况下,值为显式定义为字符串。我必须假设这可能来自下拉列表,或者web服务,或者原始JSON字符串。

在这种情况下,简单的强制转换<Fruit> fruitStringfruitString as Fruit是唯一的解决方案(参见其他答案)。您永远无法在编译时对此进行改进。(编辑:查看我关于<const>的其他答案) !

然而,当在代码中使用并不打算为字符串类型常量时,很容易遇到同样的错误。我的回答集中在第二种情况:


首先:为什么'神奇'字符串常数往往比enum更好?

  • 我喜欢字符串常量相对于枚举的方式——它紧凑而“javascript”
  • 如果您正在使用的组件已经使用字符串常量,则更有意义。
  • 为了获得枚举值而导入“枚举类型”本身就很麻烦
  • 无论我做什么,我都希望它是编制安全,所以如果我从联合类型中添加了一个有效值,或者键入错误,那么它必须给出一个编译错误。

幸运的是当你定义:

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

...你实际上是在定义一个类型联合,其中'missing'实际上是一个类型!

我经常遇到'不可赋值'错误,如果我的typescript和编译器认为中有一个像'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!

为什么?这是typescript,所以<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!

另一种解决这个问题的方法是强制转换父对象:

我的定义如下:

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

假设我们得到了一个错误(string not assignable error):

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

我们可以像这样将整个对象“断言”为FieldErrorType:

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

然后我们避免了<'invalid'> 'invalid'

那么错别字呢?<FieldErrorType>不只是断言,不管右边是什么类型。在这种情况下不会——幸运的是,如果你这样做,编译器会抱怨,因为它足够聪明,知道这是不可能的:

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

Typescript 3.4引入了新的'const'断言

您现在可以防止文字类型(例如。'orange''red')被“拓宽”为类型string,使用所谓的const断言。

你将能够做到:

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

然后它就不会再变成string了——这就是问题的根源。

你也可以在整个对象上这样做:

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


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

小贴士:你也可以使用<const> false<const> true来表示一个必须为真或假的布尔值。这在歧视工会中有时是有用的。你一看到就知道了。

例如,如果在模拟数据时转换为dropdownvalue[],则将其组合为具有value和display属性的对象数组。

例子:

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

我也遇到了同样的问题,我做了以下修改,问题得到了解决。

打开watchQueryOptions.d.ts文件

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

将查询类型任何改为DocumentNode,与突变相同

之前:

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

后:

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

以上所有答案都是有效的,然而,在某些情况下,字符串文字类型是另一个复杂类型的一部分。考虑下面的例子:

  // 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)第一个解决方案是为size定义一个类型,并从foot .ts导出它。当您需要单独使用size参数时,这很好。例如,您有一个函数接受或返回类型大小的参数,而您想输入它。

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

当传递道具到React组件时,我也遇到了类似的问题。

原因:My type inference on myArray wasn't working correctly

< a href = " https://codesandbox.io/s/type-string-issue-fixed-z9jth?file=/src/App。tsx" rel="nofollow noreferrer">https://codesandbox.io/s/type-string-issue-fixed-z9jth?file=/src/App.tsx

特别感谢Reactiflux的Brady在这个问题上的帮助。

在扩散的数组中,这个错误仍然会被抛出:

export type Fruit = "Orange" | "Apple" | "Banana"
export type FruitArray = Fruit[];


const someFruits= ["Banana"];


const workingFruits: FruitArray = ["Orange", "Apple"]; // Works


// Even orange and apple show: Type 'string' is not assignable to type Fruit
const brokenAllFruits: FruitArray = [...someFruits, "Orange", "Apple"];


// As const is needed in the spread array
const someConstFruits= ["Banana" as const];
const workingAllFruits: FruitArray = [...someConstFruits, "Orange", "Apple"]; // Works

这个问题被标记为Angular,尽管它实际上与Angular没有任何关系。然而,有(至少)一个Angular特定的情况,你可能会意外地得到这个错误。

  • 如果你已经禁用了strictNullInputTypes
  • 如果你使用诸如Fruit这样的文字类型作为@Input() . c
  • 你试图将'Orange'传递给一个输入,它被解释为一个字符串。

这将在Angular 13.1中修复。

https://github.com/angular/angular/pull/38305

如果你正在与类一起工作,你可以做以下其中之一:

示例模型:

type Fruit = 'Apple' | 'Banana';


interface ClassWithFruit  {
fruit: Fruit;
}

实现模型的类,这里有三个选项:

class MyClass implements ClassWithFruit {
// option 1
fruit = 'Apple' as const;


// option 2
fruit = <const>'Apple';
  

// option 3
readonly fruit = 'Apple';
}