这是我在fruit.ts中的内容
export type Fruit = "Orange" | "Apple" | "Banana"
现在我在另一个打字稿文件中导入fruit.ts。这就是我所拥有的
myString:string = "Banana";
myFruit:Fruit = myString;
当我做
myFruit = myString;
我收到一个错误:
类型“字符串”不可分配给类型“橙色”| “苹果” | “香蕉”'
如何将字符串分配给自定义类型 Fruit 的变量?
更新
正如@Simon_Weaver 的回答中提到的,从 TypeScript 3.4 版开始,可以将其断言为 const
:
let fruit = "Banana" as const;
旧答案
您需要cast it:
export type Fruit = "Orange" | "Apple" | "Banana";
let myString: string = "Banana";
let myFruit: Fruit = myString as Fruit;
另请注意,使用 string literals 时,您只需使用一个 |
Typescript 3.4
引入了新的“const”断言
您现在可以使用所谓的 const
断言防止文字类型(例如 'orange'
或 'red'
)被“扩展”为类型 string
。
您将能够:
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
来表示必须为真或假的布尔值。这有时在 discriminated unions 中很有用。当你看到它时你就会知道。
let fruit = 'orange' as const;
当你这样做时:
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
的实际 type 是 "Banana"
。是的,"Banana"
是 类型。它扩展了 String
,因此可以分配给 String
。此外,当类型扩展任何其组件时,类型扩展联合类型。在这种情况下,类型 "Banana"
扩展了 "Orange" | "Apple" | "Banana"
,因为它扩展了它的一个组件。因此,"Banana"
可分配给 "Orange" | "Apple" | "Banana"
或 Fruit
。
<'Banana'> 'Banana'
,这会将 "Banana"
字符串“转换”为 "Banana"
类型!
<const> 'Banana'
哪个更好:-)
有几种情况会给你这个特定的错误。在 OP 的情况下,有一个明确定义为字符串的值。所以我不得不假设这可能来自下拉列表、Web 服务或原始 JSON 字符串。
在这种情况下,简单的强制转换 <Fruit> fruitString
或 fruitString as Fruit
是唯一的解决方案(请参阅其他答案)。您将永远无法在编译时对此进行改进。 [编辑:查看我关于 <const>
的其他答案]!
但是,在代码中使用原本不打算为字符串类型的常量时,很容易遇到同样的错误。我的回答集中在第二种情况:
首先:为什么“魔术”字符串常量通常比枚举更好?
我喜欢字符串常量与枚举的外观——它紧凑且“javascripty”
如果您正在使用的组件已经使用字符串常量,则更有意义。
仅仅为了获取枚举值而必须导入“枚举类型”本身就很麻烦
无论我做什么,我都希望它是编译安全的,所以如果我从联合类型中删除一个有效值,或者输入错误,那么它必须给出一个编译错误。
幸运的是,当您定义:
export type FieldErrorType = 'none' | 'missing' | 'invalid'
...您实际上是在定义一个 类型联合,其中 'missing'
实际上是一个类型!
如果我的打字稿中有一个像 'banana'
这样的字符串,并且编译器 认为 我的意思是它是一个字符串,而我真的希望它是 'banana'
类型,我经常会遇到“不可分配”错误2}。编译器的智能程度取决于代码的结构。
这是我今天收到此错误的示例:
// 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!
解决问题的另一种方法是强制转换父对象:
我的定义如下:
导出类型字段名 = '数字' | '过期日期' | 'cvv';导出类型 FieldError = 'none' | '失踪' | '无效的';导出类型 FieldErrorType = { 字段:字段名称,错误:字段错误 };
假设我们得到一个错误(字符串不可分配错误):
fieldErrors: [ { field: 'number', error: 'invalid' } ]
我们可以像这样将整个对象“断言”为 FieldErrorType
:
fieldErrors: [ <FieldErrorType> { field: 'number', error: 'invalid' } ]
然后我们避免必须做<'invalid'> 'invalid'
。
但是错别字呢? <FieldErrorType>
不只是断言 任何属于该类型的权利。在这种情况下不是 - 幸运的是,如果你这样做,编译器 WILL 会抱怨,因为它足够聪明,知道这是不可能的:
fieldErrors: [ <FieldErrorType> { field: 'number', error: 'dog' } ]
我看到这有点旧,但这里可能有更好的解决方案。
如果您想要一个字符串,但您希望该字符串只匹配某些值,您可以使用 enums。
例如:
enum Fruit {
Orange = "Orange",
Apple = "Apple",
Banana = "Banana"
}
let myFruit: Fruit = Fruit.Banana;
现在您将知道无论如何,myFruit 将始终是字符串“Banana”(或您选择的任何其他可枚举值)。这对很多事情都很有用,无论是将类似的值分组,还是将用户友好的值映射到机器友好的值,同时强制和限制编译器允许的值。
let myFruit: Fruit = "Banana"
。
以上所有答案都是有效的,但是,在某些情况下,字符串文字类型是另一种复杂类型的一部分。考虑以下示例:
// 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 中导出。如果您需要自己使用 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)
));
在传播此错误的数组中,仍然可能会产生一些误导:
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
在将道具传递给 React 组件时,我遇到了类似的问题。
原因:My type inference on myArray wasn't working correctly
https://codesandbox.io/s/type-string-issue-fixed-z9jth?file=/src/App.tsx
特别感谢 Reactiflux 的 Brady 帮助解决这个问题。
例如,如果您在模拟数据时转换为 dropdownvalue[]
,请将其组合为具有值和显示属性的对象数组。
例子:
[{'value': 'test1', 'display1': 'test display'},{'value': 'test2', 'display': 'test display2'},]
这个问题被标记为 Angular,即使它与 Angular 没有任何关系。但是,(至少)有一个 Angular 特定情况,您可能会意外收到此错误。
如果您禁用了 strictNullInputTypes
如果您使用诸如 Fruit 之类的文字类型作为 @Input()
您尝试将“橙色”传递给输入,它会被解释为字符串。
这将在 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';
}
我收到了同样的错误消息,但场景略有不同。我来这里寻找答案,但没有一个答案对我有用,所以我会为其他人分享我的解决方案。我没有自定义类型,它只是一个字符串数组。我有一个带有字符串属性 abcOrD 的对象 myObject 必须是字符串数组中的值之一,例如“a”“b”“c”或“d ”。尝试分配 myObject.abcOrD = myStringVar
时,它会给我一条错误消息 Type 'string' is not assignable to type "a" | "b" | "c" | "d"
。在玩耍并尝试了一些对我有用的东西之后,as any:
myObject.abcOrD = myStringVar as any;
我遇到了同样的问题,我做了以下更改,问题得到了解决。
打开 watchQueryOptions.d.ts 文件
\apollo-client\core\watchQueryOptions.d.ts
将查询类型更改为任何而不是 DocumentNode,突变相同
前:
export interface QueryBaseOptions<TVariables = OperationVariables> {
query: **DocumentNode**;
后:
export interface QueryBaseOptions<TVariables = OperationVariables> {
query: **any**;
const myFruit: Fruit = "Banana"
会做。let myFruit:Fruit = "Apple" let something:string = myFruit as string
它给了我错误:将“水果”类型转换为“字符串”类型可能是一个错误。as string
部分。我在操场上试过你的代码,没有错误。as const
选项?const myFruit: Fruit = 'Bananaaa' as const;
确实会引发编译错误,而const myFruit: Fruit = 'Bananaaa' as Fruit;
不会。 Simon_Weaver 的答案应该是新接受的答案,您介意编辑答案以包含新的 const 断言吗?