Are there any incantations to properly type the following function in typescript?
given a function
createMap()
that takes:
- a prefix (e.g.
foo
)- and a tuple of suffixes (e.g. ['a', 'b', 'c'])
calling
createMap('foo', ['a', 'b', 'c'])
return the following mapped type:{ a: 'foo.a', b: 'foo.b', c: 'foo.c', }
The function implementation is pretty straightforward, but the return type doesn't seem to be:
const createMap = <P extends string, S extends string[]>(prefix: P, suffixes: [...S]) => {
return suffixes.reduce((map, suffix) => ({
...map,
[suffix]: `${prefix}.${suffix}`,
}), {});
}
I know this is incorrect, but it would naively look something like { [K in S]:
${P}.${K in S} }
.
CodePudding user response:
In order to achieve it you must set the list as const
then you can do the following:
type CreateMap = <S extends string, A extends ReadonlyArray<string>>(s: S, a: A) => {
[K in (A extends ReadonlyArray<infer U> ? U : never)]: `${S}.${K}`
};
const createMap: CreateMap = (s, a) => {
return Object.fromEntries(a.map(key => [key, `${s}.${key}`])) as any
}
const list = ['a', 'b', 'c'] as const
const res = createMap('foo', list)
You can see an example here
CodePudding user response:
We can use a mapped type to compute the return type of the function. We only need to map over the elements in S
by computing the union S[number]
. Each element K
can now be used to concatenate the prefix P
with a template literal type.
const createMap = <
P extends string,
S extends string[]
>(prefix: P, suffixes: [...S]) => {
return suffixes.reduce((map, suffix) => ({
...map,
[suffix]: `${prefix}.${suffix}`,
}), {}) as { [K in S[number]]: `${P}.${K}` }
}
const result = createMap('foo', ['a', 'b', 'c'])
// ^? const result: { a: "foo.a"; b: "foo.b"; c: "foo.c"; }