【问题标题】:Typescript Type 'string' is not assignable to type打字稿类型“字符串”不可分配给类型
【发布时间】:2016-10-25 00:21:41
【问题描述】:

这是我在fruit.ts 中的内容

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

现在我在另一个打字稿文件中导入fruit.ts。这就是我所拥有的

myString:string = "Banana";

myFruit:Fruit = myString;

当我这样做时

myFruit = myString;

我收到一个错误:

类型“字符串”不可分配给类型“橙色”| “苹果” | “香蕉”'

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

【问题讨论】:

    标签: javascript typescript angular


    【解决方案1】:

    如果您正在使用类,您可以这样做,例如以下之一:

    示例模型:

    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';
    }
    

    【讨论】:

      【解决方案2】:

      这个问题被标记为 Angular,尽管它与 Angular 没有任何关系。但是,(至少)有一个 Angular 特定情况,您可能会意外收到此错误。

      • 如果您禁用了strictNullInputTypes
      • 如果您使用诸如Fruit 之类的文字类型作为@Input()
      • 您尝试将'Orange' 传递给输入,它被解释为字符串。

      这将在 Angular 13.1 中修复。

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

      【讨论】:

        【解决方案3】:

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

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

        你将能够做到:

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

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

        额外提示:您还可以使用&lt;const&gt; false&lt;const&gt; true 来表示必须为真或假的布尔值。这有时在discriminated unions 中很有用。看了就知道了。

        【讨论】:

        • 适合像我一样还没有使用 3.4 的人。 可以被任何替换,但当然不如这个解决方案干净。
        • 遵循 no-angle-bracket-type-assertion 规则时,首选语法是 let fruit = 'orange' as const;
        • 这是现代 Typescript 的正确答案。它可以防止不必要的类型导入,并让结构类型正确地完成它的工作。
        【解决方案4】:

        在传播此错误的数组中仍然可能会引发一些误导:

        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
        

        【讨论】:

          【解决方案5】:

          我收到了同样的错误消息,但情况略有不同。我来这里寻找答案,但没有一个答案对我有用,所以我会为其他人分享我的解决方案。我没有自定义类型,它只是一个字符串数组。我有一个带有字符串属性 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;
          

          【讨论】:

            【解决方案6】:

            在将 props 传递给 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 帮助解决此问题。

            【讨论】:

              【解决方案7】:

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

                // 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 参数,这很好。例如,您有一个函数接受或返回一个 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)
                ));
              

              【讨论】:

                【解决方案8】:

                你需要cast it:

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

                还要注意,当使用string literals时,你只需要使用一个|

                编辑

                正如@Simon_Weaver 在另一个答案中所提到的,现在可以将其声明为const

                let fruit = "Banana" as const;
                

                【讨论】:

                • 不错的一个:) 在大多数情况下const myFruit: Fruit = "Banana" 会做。
                • 我可以反过来吗?我的意思是这样的let myFruit:Fruit = "Apple" let something:string = myFruit as string 它给了我错误:将“水果”类型转换为“字符串”类型可能是一个错误。
                • @Siraj 它应该可以正常工作,您甚至不需要 as string 部分。我在操场上试过你的代码,没有错误。
                • @usagidon 您是否尝试过其他答案中描述的as const 选项?
                • 感谢 Nitzan,确实 const myFruit: Fruit = 'Bananaaa' as const; 确实会引发编译错误,而 const myFruit: Fruit = 'Bananaaa' as Fruit; 不会。 Simon_Weaver 的答案应该是新接受的答案,您介意编辑答案以包含新的 const 断言吗?
                【解决方案9】:

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

                打开watchQueryOptions.d.ts文件

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

                更改查询类型any而不是DocumentNode,突变相同

                之前:

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

                之后:

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

                【讨论】:

                  【解决方案10】:

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

                  示例

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

                  【讨论】:

                    【解决方案11】:

                    有几种情况会出现此特定错误。对于 OP,有一个值明确定义为字符串。所以我不得不假设这可能来自下拉列表、Web 服务或原始 JSON 字符串。

                    在这种情况下,简单的演员&lt;Fruit&gt; fruitStringfruitString as Fruit 是唯一的解决方案(请参阅其他答案)。您将永远无法在编译时对此进行改进。 [编辑:查看我关于&lt;const&gt;的其他答案]!

                    但是,在您的代码中使用从未打算成为字符串类型的常量时,很容易遇到同样的错误。我的回答集中在第二种情况:


                    首先:为什么'magic'字符串常量通常比枚举更好?

                    • 我喜欢字符串常量与枚举相比的外观 - 它紧凑且“javascripty”
                    • 如果您正在使用的组件已经使用字符串常量,则更有意义。
                    • 仅仅为了获取枚举值而必须导入“枚举类型”本身就很麻烦
                    • 无论我做什么,我都希望它编译安全,所以如果我从联合类型中删除一个有效值,或者输入错误,那么它必须给出一个编译错误。

                    幸运的是当你定义:

                    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!
                    

                    为什么?这是打字稿,所以&lt;FieldErrorType&gt; 是一个断言,你告诉编译器狗是一个 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' | '过期日期' | 'cvv'; 导出类型 FieldError = 'none' | '失踪' | '无效的'; 导出类型 FieldErrorType = { field: FieldName, error: FieldError };

                    假设我们得到一个错误(字符串不可分配错误):

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

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

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

                    那我们就不用&lt;'invalid'&gt; 'invalid'了。

                    但是错别字呢? &lt;FieldErrorType&gt; 不只是 assert 任何属于那种类型的权利。在这种情况下不是 - 幸运的是,如果你这样做,编译器 抱怨,因为它足够聪明,知道这是不可能的:

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

                    【讨论】:

                    • 严格模式可能存在细微差别。确认后会更新答案。
                    【解决方案12】:

                    当你这样做时:

                    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

                    【讨论】:

                    • 有趣的是,您实际上可以输入&lt;'Banana'&gt; 'Banana',这会将"Banana" 字符串“转换”为"Banana" 类型!!!
                    • 但是现在你可以做&lt;const&gt; 'Banana' 哪个更好:-)
                    【解决方案13】:

                    我发现这有点旧,但这里可能有更好的解决方案。

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

                    例如:

                    enum Fruit {
                        Orange = "Orange",
                        Apple  = "Apple",
                        Banana = "Banana"
                    }
                    
                    let myFruit: Fruit = Fruit.Banana;
                    

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

                    【讨论】:

                    • 这很有趣,因为我在试图避免这样做时遇到了这个问题。它越来越让我发疯。我正在尝试成为“javascripty”并使用限制为类型(而不是枚举)的魔术字符串,它似乎越来越适得其反,并在我的整个应用程序中出现此错误:-/
                    • 这也会导致相反的问题。你不能再做let myFruit: Fruit = "Banana"
                    猜你喜欢
                    • 2022-01-17
                    • 2020-02-07
                    • 2021-11-10
                    • 2019-07-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-10-11
                    • 2019-04-10
                    • 2021-09-21
                    相关资源
                    最近更新 更多