How to define string literal union type from constants in Typescript
Asked Answered
C

4

112

I know I can define string union types to restrict variables to one of the possible string values:

type MyType = 'first' | 'second'
let myVar:MyType = 'first'

I need to construct a type like that from constant strings, e.g:

const MY_CONSTANT = 'MY_CONSTANT'
const SOMETHING_ELSE = 'SOMETHING_ELSE'
type MyType = MY_CONSTANT | SOMETHING_ELSE

But for some reason it doesn't work; it says MY_CONSTANT refers to a value, but it being used as a type here.

Why does Typescript allow the first example, but doesn't allow the second case? I'm on Typescript 3.4.5

Choler answered 22/5, 2019 at 18:50 Comment(5)
Sounds like what you really want is an enum...?Pentup
You want type MyType = typeof MY_CONSTANT | typeof SOMETHING_ELSE. There's a big difference between types (which exist only at design time) and values (which exist at runtime)Salley
I often throw this answer at people when I sense confusion between types and values.Salley
@T.J.Crowder considered that, but I've got constant strings that are defined elsewhere that I want to useWifeless
@CanPoyrazoğlu - Fair enough, then Titian's answer is what you want.Pentup
S
155

To get the type of a variable you need to use the typeof type operator:

const MY_CONSTANT = 'MY_CONSTANT' // must be const, no annotation. let or var will not work
const SOMETHING_ELSE = 'SOMETHING_ELSE' // must be const, no annotation. let or var will not work
type MyType = typeof MY_CONSTANT | typeof SOMETHING_ELSE

Playground

Note:

Since there seems to be a lot of confusion when people use this. The const matters. If you use other types of declarations (let or var) the final type would be string. Only const preserves string literal types.

Note 2:

For this solution to work you must not specify any type annotation on the const, and let the compiler infer the type of the constants (ex this will not work :const MY_CONSTANT: string = 'MY_CONSTANT')

Secco answered 22/5, 2019 at 18:54 Comment(16)
yup, that worked. though I still don't get why I can create some string inline like 'test' and use it directly, but need a typeof keyword if it's a reference.Wifeless
@CanPoyrazoğlu 'test' is both a value and a type (a string literal type), by definition (just like a class is a value, the constructor, and a type, the instance type)Secco
typeof MY_CONSTANT will check if it is a whatever string, not specifically the MY_CONSTANTWestney
@knuhol not sure I understand what you mean here. typeof MY_CONSTANT will give you the type of the constant, whatever that is. If you defined it in such a way that the type of it is string it will continue to be a stringSecco
If someone can explain the down-votes on this answer I would be grateful. I stand by the answer, it still does what it is supposed to and is an answer to the original question. If someone has issues with it let me knowSecco
@knuhol see the note about const I think this is why you were getting stringSecco
Titian - the problem with your solution is that ANY string is a valid MyType.Midstream
@JonasRosenqvist please read the comments and the notes. Please check the playground link associated. If you use const then the literal type is preserved and type checking works as you expect and not all strings are compatible. If you have a sample where this is not true I would like to see it. Sorry if I sound a bit mad, but this answer keeps getting downvotes for no reason and it's getting very annoying. Not sure what I can do to make it more clear const is requiredSecco
@TitianCernicova-Dragomir - you're right, I had made the mistake of declaring the constant as: const MY_CONSTANT:string = 'MY_CONSTANT' Specifying the type of constant made it accept any string. Sorry, you'll get my upvote!Midstream
@JonasRosenqvist That is important feedback, I'll add it in the notes for the next person reading it. Thank you.Secco
Important Note: It doesn't work when defining your strings with template literals. Then the types are just stringWainscot
@Wainscot not true, as long as your string are constants, the delimiter does not matter. If you use interpolation then yes, they will be strings in 4.0: typescriptlang.org/play?#code/…Secco
@TitianCernicova-Dragomir Thanks for pointing that out. I indeed used a delimiter. I'm wondering why Typescript can't infer that the entire value will also be a string.Wainscot
The problem with this that Pick<MyType, 'MY_CONSTANT'> will raise an error since Pick will try to extract the String prototypes rather than the actual stringsInnumerable
Regarding The const matters; what about let MY_CONSTANT: 'MY_CONSTANT' (notice the colon)?Matthieu
This seems to be the correct answer indeed and it includes crucial details on how to get this right. But it goes to show that bolting a proper type system onto a language that originally didn't have one (that is, creating TypeScript on top of JavaScript) can result in something ugly and overly complex.Fisherman
H
21

You can also use enum for this case. For example:

// Define enum.
enum myConstants {
  MY_CONSTANT = 'my_constant',
  SMTH_ELSE = 'smth_else'
}

// Use it in an interface for typechecking.
interface MyInterface {
  myProp: myConstants
}

// Example of correct object - no errors.
let a: MyInterface = {
  myProp: myConstants.MY_CONSTANT
}

// Incorrect value - TS reports an error.
let b: MyInterface = {
  myProp: 'John Doe'
}

More about enums

Hohenstaufen answered 17/4, 2020 at 9:26 Comment(1)
This is exactly what I was looking for.Shamefaced
N
13

Enums cover the case quiet well:

export enum ITEM_TYPES {
    TYPE1 = 'text',
    TYPE2 = 'image'
}

export type IItemType = ITEM_TYPES.TYPE1 | ITEM_TYPES.TYPE2

And then in code ITEM_TYPES can be refered to for all kind of runtime comparisons:

if (type === ITEM_TYPES.TYPE1){
}
Niel answered 13/10, 2021 at 8:8 Comment(0)
S
0

I made a post related to string literal union type. https://medium.com/@m.fatihalaziz/string-literal-union-type-with-typeguard-in-typescript-dff4c9741b4a

what it cover:

  • how to create a string literal union type
  • how to pass string as the string literal
  • how to use the type guard of typescript effectively

I hope it helps, Good Luck!

Scribble answered 5/7, 2022 at 20:56 Comment(2)
Thank you Fatih. Just read your article, it's very detailed and informative!Wifeless
I'm so glad my post can help you, thanks!Scribble

© 2022 - 2024 — McMap. All rights reserved.