添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
另类的单车  ·  如何卸载.vsix ...·  2 月前    · 
气势凌人的眼镜  ·  iOS ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Now I'm importing fruit.ts in another typescript file. Here's what I have

myString:string = "Banana";
myFruit:Fruit = myString;

When I do

myFruit = myString;

I get an error:

Type 'string' is not assignable to type '"Orange" | "Apple" | "Banana"'

How can I assign a string to a variable of custom type Fruit?

Update

As mentioned in @Simon_Weaver's answer, since TypeScript version 3.4 it's possible to assert it to const:

let fruit = "Banana" as const;

Old answer

You'll need to cast it:

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

Also notice that when using string literals you need to use only one |

Can I do its reverse? I mean something like this let myFruit:Fruit = "Apple" let something:string = myFruit as string It is giving me error: Conversion of type 'Fruit' to type 'string' may be a mistake. – Siraj Alam Dec 17, 2018 at 10:19 @Siraj It should work just fine, you don't even need the as string part. I've tried your code in playground and there are no errors. – Nitzan Tomer Dec 17, 2018 at 10:25 Thanks Nitzan, indeed const myFruit: Fruit = 'Bananaaa' as const; does throw compilation errors whereas const myFruit: Fruit = 'Bananaaa' as Fruit; does not. The answer by Simon_Weaver should be the new accepted answer, would you mind editing your answer to include the new const assertion? – blub Oct 9, 2019 at 18:28

Typescript 3.4 introduced the new 'const' assertion

You can now prevent literal types (eg. 'orange' or 'red') being 'widened' to type string with a so-called const assertion.

You will be able to do:

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

And then it won't turn itself into a string anymore - which is the root of the problem in the question.

You can also do it on a whole object:

let animals = [ { species: 'dog' }, { species: 'cat' } ] as const;
type firstAnimal = (typeof animals)[0]['species'];  // string literal 'dog'

Bonus Tip: You can also use <const> false or <const> true to represent a boolean that must be true or false. This can be useful in discriminated unions sometimes. You'll know it when you see it.

For people who, like me, aren't on 3.4 yet. <const> can be replaced by any, but is of course not as clean as this solution. – Pieter De Bie May 15, 2019 at 7:46 Prefered syntax would be let fruit = 'orange' as const; when following no-angle-bracket-type-assertion rule – ThunderDev Jun 12, 2019 at 9:55 This is the correct answer for modern Typescript. It prevents unneeded import of types and lets structural typing do its thing correctly. – James McMahon Sep 19, 2019 at 16:59

...you are creating a type called Fruit that can only contain the literals "Orange", "Apple" and "Banana". This type extends String, hence it can be assigned to String. However, String does NOT extend "Orange" | "Apple" | "Banana", so it cannot be assigned to it. String is less specific. It can be any string.

When you do this:

export type Fruit = "Orange" | "Apple" | "Banana"
const myString = "Banana";
const myFruit: Fruit = myString;

...it works. Why? Because the actual type of myString in this example is "Banana". Yes, "Banana" is the type. It is extends String so it's assignable to String. Additionally, a type extends a Union Type when it extends any of its components. In this case, "Banana", the type, extends "Orange" | "Apple" | "Banana" because it extends one of its components. Hence, "Banana" is assignable to "Orange" | "Apple" | "Banana" or Fruit.

It's funny you can actually put <'Banana'> 'Banana' and that will 'cast' a "Banana" string to "Banana" the type !!! – Simon_Weaver Dec 15, 2018 at 1:56

There are several situations that will give you this particular error. In the case of the OP there was a value defined explicitly as a string. So I have to assume that maybe this came from a dropdown, or web service or raw JSON string.

In that case a simple cast <Fruit> fruitString or fruitString as Fruit is the only solution (see other answers). You wouldn't ever be able to improve on this at compile time. [Edit: See my other answer about <const>] !

However it's very easy to run into this same error when using constants in your code that aren't ever intended to be of type string. My answer focuses on that second scenario:

First of all: Why are 'magic' string constants often better than an enum?

  • I like the way a string constant looks vs. an enum - it's compact and 'javascripty'
  • Makes more sense if the component you're using already uses string constants.
  • Having to import an 'enum type' just to get an enumeration value can be troublesome in itself
  • Whatever I do I want it to be compile safe so if I add remove a valid value from the union type, or mistype it then it MUST give a compile error.
  • Fortunately when you define:

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

    ...you're actually defining a union of types where 'missing' is actually a type!

    I often run into the 'not assignable' error if I have a string like 'banana' in my typescript and the compiler thinks I meant it as a string, whereas I really wanted it to be of type banana. How smart the compiler is able to be will depend on the structure of your code.

    Here's an example of when I got this error today:

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

    As soon as I found out that 'invalid' or 'banana' could be either a type or a string I realized I could just assert a string into that type. Essentially cast it to itself, and tell the compiler no I don't want this to be a string!

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

    So what's wrong with just 'casting' to FieldErrorType (or Fruit)

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

    It's not compile time safe:

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

    Why? This is typescript so <FieldErrorType> is an assertion and you are telling the compiler a dog is a FieldErrorType! And the compiler will allow it!

    BUT if you do the following, then the compiler will convert the string to a type

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

    Just watch out for stupid typos like this:

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

    Another way to solve the problem is by casting the parent object:

    My definitions were as follows:

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

    Let's say we get an error with this (the string not assignable error):

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

    We can 'assert' the whole object as a FieldErrorType like this:

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

    Then we avoid having to do <'invalid'> 'invalid'.

    But what about typos? Doesn't <FieldErrorType> just assert whatever is on the right to be of that type. Not in this case - fortunately the compiler WILL complain if you do this, because it's clever enough to know it's impossible:

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

    I see this is a little old, but there might be a better solution here.

    When you want a string, but you want the string to only match certain values, you can use enums.

    For example:

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

    Now you'll know that no matter what, myFruit will always be the string "Banana" (Or whatever other enumerable value you choose). This is useful for many things, whether it be grouping similar values like this, or mapping user-friendly values to machine-friendly values, all while enforcing and restricting the values the compiler will allow.

    It's funny because I get this issue when trying to get away from doing this. It's increasingly driving me nuts. I'm trying to be 'javascripty' and use magic strings constrained to a type (instead of an enumeration) and it seems to backfire more and more and ripple through my whole application with this error :-/ – Simon_Weaver Dec 15, 2018 at 1:54 This also causes the opposite problem. You can no longer do let myFruit: Fruit = "Banana". – Sean Burton Jan 28, 2020 at 15:54

    All the above answers are valid, however, there are some cases that the String Literal Type is part of another complex type. Consider the following example:

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

    You have multiple solutions to fix this. Each solution is valid and has its own use cases.

    1) The first solution is to define a type for the size and export it from the foo.ts. This is good if when you need to work with the size parameter by its own. For example, you have a function that accepts or returns a parameter of type size and you want to type it.

      // 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) The second option is to just cast it to the type ToolbarTheme. In this case, you don't need to expose the internal of ToolbarTheme if you don't need.

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

    In arrays with spreading this error can still be thrown a bit misleadingly:

    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
    

    I had a similar issue when passing props to a React Component.

    Reason: My type inference on myArray wasn't working correctly

    https://codesandbox.io/s/type-string-issue-fixed-z9jth?file=/src/App.tsx

    Special thanks to Brady from Reactiflux for helping with this issue.

    If you're casting to a dropdownvalue[] when mocking data for example, compose it as an array of objects with value and display properties.

    example:

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

    This question was tagged Angular even though it's not really anything to do with Angular. However there is (at least) one Angular specific case where you may get this error unexpectedly.

  • If you have disabled strictNullInputTypes
  • If you use a literal type such as Fruit as an @Input()
  • You try to pass 'Orange' to an input and it gets interpreted as a string.
  • This will be fixed in Angular 13.1.

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

    If you are working with classes you could do e.g. one of the following:

    Example model:

    type Fruit = 'Apple' | 'Banana';
    interface ClassWithFruit  {
      fruit: Fruit;
    

    Class that implements model, here are three options:

    class MyClass implements ClassWithFruit {
      // option 1
      fruit = 'Apple' as const;
      // option 2
      fruit = <const>'Apple';
      // option 3
      readonly fruit = 'Apple';
    

    I was facing the same issue, I made below changes and the issue got resolved.

    Open watchQueryOptions.d.ts file

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

    Change the query type any instead of DocumentNode, Same for mutation

    Before:

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

    After:

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

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.