The problem: You have a file containing many constants that you'd like to create a string union from the names or values of.
The solution: Use keyof
and typeof
to create a union from the keys and values of an object.
Example#
in constaints.ts
1export const MY_NAME = 'Trevor';
2export const I_LIKE = 'Music';
3export const MY_CAT = 'IS_CUTE';
in some other typescript file:
1import * as Constants from './constants';
2
3type ValueOf<T> = T[keyof T];
4
5// 'MY_NAME' | 'I_LIKE' | 'MY_CAT';
6export type KeyUnionOfConstants = keyof typeof Constants;
7
8// 'Trevor' | 'Music' | 'IS_CUTE';
9export type ValueUnionOfConstants = ValueOf<typeof Constants>;
Tada!
Explanation#
But why does this work?
import * as Constants from './constants';
imports each export
from constants.ts
as a key/value pair in an object that looks like this:
1{
2 MY_NAME: 'Trevor',
3 I_LIKE: 'Music',
4 MY_CAT: 'IS_CUTE'
5}
typeof Constants
is just that object expressed as a type, which looks identical.
1type Constants = {
2 MY_NAME: 'Trevor';
3 I_LIKE: 'Music';
4 MY_CAT: 'IS_CUTE';
5}
keyof typeof Constants
is each unique key in that type: MY_NAME
, I_LIKE
, and MY_CAT
.
ValueOf
is slightly more complicated, but essentially it's a way of saying "Get each key
from some type called T
, and use it to get the value inside T
" or another way of thinking about it:
1Object.values({
2 MY_NAME: 'Trevor',
3 I_LIKE: 'Music',
4 MY_CAT: 'IS_CUTE'
5}); // ['Trevor', 'Music', 'IS_CUTE']
ValueOf
is doing something similar but with type
information instead of actual runtime values. 'Give me the values contained inside a given object generically represented as T
'.