Deriving types from data in TypeScript
A TypeScript pattern I like to use is to derive types from data. Types in TypeScript only exist at build time, so this pattern has the benefit of types being automatically linked to the runtime values they represent.
Here’s one example of what I mean by that:
typeof
operator
The heart of this pattern is the typeof
operator,
which gives us the type of a variable, for example:
const
vs let
The result of typeof
will differ depending on how we declare our variable —
whether we used const
or let
.
As you could see, when we used const
, the Hello
type was the literal string
"hello"
— this is because a const
variable can never be re-assigned later,
so it can only be "hello
” at runtime as well.
What happens if we use let
?
The Hello
type gets
“widened”
to the string
type — because now the variable can be re-assigned to any
other value of type string
in another part of the program!
const
assertions
We could achieve the same effect of using const
with a const
assertion:
let hello = "hello" as const ;type Hello = typeof hello ; hello = 'Hello'Type '"Hello"' is not assignable to type '"hello"'.2322Type '"Hello"' is not assignable to type '"hello"'.
We’ll often use const
assertions to prevent type widening when deriving types
from data — i.e., avoiding a more specific (narrower) type being changed to a
more general (widened) type, such as a 0
being changed to a number
and a
"hello"
being changed to string
.
Deriving types from arrays
Suppose we have an array of strings and we want a type that is an union of the array values.
The result of typeof arr
is just an array type, the trick here is to use
number
to index that array — it’s like saying, what are all the possible
types when I index this array by a number
?
The const
assertion is critical here — if we remove it, the type gets
widened to string
.
Deriving types from objects
Now let’s try to derive our types from an object — both getting every key or every value.
const obj = { foo : 1, bar : 2, baz : 3,} as const type ObjectKey = keyof typeof obj type ObjectValue = (typeof obj )[ObjectKey ]
To get the object keys, we need to use the keyof
operator.
For the object values, we just need to index that object with all of the object possible keys — which makes sense!
The const
assertion is also critical here, because otherwise the object value
types would have been widened to number
:
const obj = { foo : 1, bar : 2, baz : 3,} type ObjectKey = keyof typeof obj type ObjectValue = (typeof obj )[ObjectKey ]
Deriving types from array of objects
We can combine both approaches to derive type from an array of objects: