TypeScript: How to get types from arrays
2019
TypeScript
TL;DR
const animals = ['cat', 'dog', 'mouse'] as const
type Animal = typeof animals[number]
// type Animal = 'cat' | 'dog' | 'mouse'
Table of contents
The problem
It can be useful to get a type from an array of values so that you can use it as, say, the input to a function to restrict the values that can be passed to it.
Let’s take this simple, although contrived, example:
const animals = ['cat', 'dog', 'mouse']
function getAnimal(name: string) {
return animals.find(a => a === name)
}
// getAnimal('cat') // OK
// getAnimal('dog') // OK
// getAnimal('mouse') // OK
// getAnimal('elephant') // ?
Should I be able to ask for ‘elephant’?
It would be great to restrict the input to just the values in the array, especially as we know what they are:
const animals = ['cat', 'dog', 'mouse']
type Animal = 'cat' | 'dog' | 'mouse'
function getAnimal(name: Animal) {
return animals.find(a => a === name)
}
// getAnimal('cat') // OK
// getAnimal('dog') // OK
// getAnimal('mouse') // OK
// getAnimal('elephant') // Type error
Now we get a handy type error so that we know we are passing in the wrong value:
Argument of type ‘“elephant”’ is not assignable to parameter of type ‘Animal’.
Great, but we don’t want to be duplicating all of the values in our array as a type.
Let’s generate a type from our array instead 🚀
Inferred array types
const animals = ['cat', 'dog', 'mouse']
// const animals: string[]
In the above, animals
has the inferred type string[]
as we have initialised the array with strings.
If we initialised the array with another type(s), say numbers const animals = [5, 10, 20]
, then TypeScript would infer the type number[]
, but lets stick to strings for this example.
Const assertions
In order to force the type not to be string[]
, and instead to be the array of values itself, we can do the following:
const animals = ['cat', 'dog', 'mouse'] as ['cat', 'dog', 'mouse']
// const animals: ['cat', 'dog', 'mouse']
We can now use const assertions (TypeScript 3.4+) to remove the duplication. So, the following is effectively the same as the above:
const animals = ['cat', 'dog', 'mouse'] as const
// const animals: readonly ['cat', 'dog', 'mouse']
Now, animals
has the type readonly ['cat', 'dog', 'mouse']
.
Getting a type from our array
Now we can get a type from our animals
array using typeof
:
const animals = ['cat', 'dog', 'mouse'] as const
type Animal = typeof animals[number]
// type Animal = 'cat' | 'dog' | 'mouse'
An array has a numeric index signature, as it’s indexed by numbers (0, 1, 2, …), so we use typeof animals[number]
to get a union type of all items in our array: 'cat' | 'dog' | 'mouse'
.
If we were to use a specific index, typeof animals[1]
, we would get a type of just the value at that index: 'dog'
.
Arrays of objects
Just in case you’re interested, if we had an array of objects instead of strings:
const animals = [
{ species: 'cat', name: 'Fluffy' },
{ species: 'dog', name: 'Fido' },
{ species: 'mouse', name: 'Trevor' }
] as const
…we would then provide the index signature for the array and the relevant object key:
type Animal = typeof animals[number]['species']
// type Animal = "cat" | "dog" | "mouse"