Issue
Is it possible to derive a union from a non-literal array?
I've tried this:
const tokens = {
"--prefix-apple": "apple",
"--prefix-orange": "orange"
};
const tokenNames = Object.keys(tokens).map(token => token.replace("--prefix-", ""));
type TokenNamesWithoutPrefix = typeof tokenNames[number]
However, it returns type string
when I would like "apple" | "orange"
Solution
You need to be able to do this operation at the type level because the built in functions you are using here just return string
types. So you need generic types that will deal with string literal types.
Let's start with this:
type StripPrefix<T extends string, Prefix extends string> =
T extends `${Prefix}${infer Result}`
? Result
: T
type Test = StripPrefix<'--prefix-foo-bar', '--prefix-'>
// ^? 'foo-bar'
This is a type that checks if a string literal type starts with a prefix, and then infers the string literal type that comes after that prefix.
If no match is found, then the type just resolves to T
, unaltered.
Now we need a function to do this operation and enforce these types.
function removePrefix<
T extends string,
Prefix extends string
>(token: T, prefix: Prefix): StripPrefix<T, Prefix> {
return token.replace(prefix, "") as StripPrefix<T, Prefix>
}
Here the token to start with is captured as type T
, and the prefix to trim off is captured as type Prefix
.
But we need a type assertion here on the return value with as
. This is because the replace
method on strings always just returns the type string
. So if you know the types going into that we can enforce something better, which is our new StripPrefix
type.
One more problem is that Object.keys
always returns string[]
and never the specific keys. This is because the actual object may have more keys than the type knows about.
const obj1 = { a: 123, b: 456 }
const obj2: { a: number } = obj1 // fine
const keys = Object.keys(obj2) // ['a', 'b']
In that snippet, obj2
has two keys, but the type only knows about one.
So if you are sure this situation will not happen, or you are sure that it won't matter if it does, then you can use another type assertion to strongly type the array of keys:
const myObjectKeys = Object.keys(tokens) as (keyof typeof tokens)[]
And then map over that with the function from before and it should work like you expect.
const tokenNames = tokenKeys.map(token => removePrefix(token, "--prefix-"));
// ^? ('apple' | 'orange')[]
Answered By - Alex Wayne
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.