36

I often use code such as

export type Stuff = 'something' | 'else'
export const AVAILABLE_STUFF: Stuff[] = ['something', 'else']

This way I can both use the type Stuff, and iterate over all the available stuffs if need be.

This works, but it feels like repeating twice the information. And you have to be careful because an update of either Stuff or AVAILABLE_STUFF also requires an update of its counterpart.

Is there a better way to define the type from the Array, or even to somewhat use the array to type some data ?

4
  • Do you want to fill the array with the types or what exactly is the use case to use the array like that? Commented Mar 6, 2019 at 10:00
  • 1
    @MuratKaragöz kind of. I would like to be able to profit from the benefits of both const x: Stuff and AVAILABLE_STUFF.forEach(stuff => ...) with only one declaration. I don't care if I have to declare an array, a type, or whatever else that I yet don't know about :) Commented Mar 6, 2019 at 10:02
  • Have you considered using a string enum? That looks like it should do what you want. It is a type, and you can iterate the values. Commented Mar 6, 2019 at 10:03
  • Closely related to stackoverflow.com/questions/51521808/… Commented Oct 5, 2020 at 17:50

3 Answers 3

47

With TypeScript v3.4 const assertions:

export const AVAILABLE_STUFF = <const> ['something', 'else'];
export type Stuff = typeof AVAILABLE_STUFF[number];
Sign up to request clarification or add additional context in comments.

3 Comments

Why does it have to be AVAILABLE_STUFF[number] and not just AVAILABLE_STUFF[] ?
Because the "keys" in arrays are number.
This nearly worked for me but I had to do ['something', 'else'] as const (as const instead of <const>) and then (typeof AVAILABLE_STUFF)[number] (added brackets).
16

One built-in option would be to use an enum instead of the type and array approach.

export enum Stuff {
    something = 'something',
    else = 'else',
}

export const AVAILABLE_STUFF: Stuff[] = Object.values(Stuff);

Another option is to extract the type from the type of AVAILABLE_STUFF. To do this we must force the compiler to infer a tuple of string literals for AVAILABLE_STUFF. This can be done in 3.4 with as const or before 3.4 using an extra function. After AVAILABLE_STUFF is a tuple type we can just use a type query to get the type of the elements:

export const AVAILABLE_STUFF = (<T extends string[]>(...o: T)=> o)('something', 'else'); // typed as ["something", "else"]
// export const AVAILABLE_STUFF = ['something', 'else'] as const; // typed as ["something", "else"]  in 3.4
export type Stuff = typeof AVAILABLE_STUFF[number] //"something" | "else"

A few explanations of the above code. typeof AVAILABLE_STUFF gives us the type of the constant (["something", "else"]) to get the [number] is called a type query and will give us the type of an item in the tuple.

The (<T extends string[]>(...o: T)=> o) is just an IIFE we use to force the compiler to infer a string literal tuple type. It has to be generic as the compiler will only infer literal types and tuples in certain cases (a type parameter with a constraint of string being one of them). The as const version is what I would recommend using when it becomes available as it is more readable.

6 Comments

In the second solution, I see that the type Stuff is correctly inferred but I don't understand the syntax. What is the role of [number] in typeof AVAILABLE_STUFF[number]?
@Paleo added some explanations let me know if it is clearer now.
Second option works for me, but I'm still puzzled about enum. when I try const x: Stuff = 'something' I get an error Type '"something"' is not assignable to type 'Stuff'. Am I doing something wrong ?
@aherve it is not a perfect replacement. you will need to use let a: Stuff = Stuff.something in TS code. At runtime the value of a will be something
@MoshFeu This should work: typescriptlang.org/play/…
|
8

Another possibility is to create an object and use keyof. I recommend that way only if you have anything useful to store as values (in replacement of null in the code below).

const stuffDict = {
  something: null,
  else: null,
}

type Stuff = keyof typeof stuffDict
const AVAILABLE_STUFF = Object.keys(stuffDict) as Stuff[]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.