I want to create a function signature like this:
countBy<R, V>(objs: R[], iteratee: ((x: R) => V) | keyof R): Map<V, number> {}
In the style of lodash, iteratee
should either be a function (x: R) => V
or the shorthand keyof R
:
countBy(pizzas, pizza => pizza.sauce)
countBy(pizzas, 'sauce')
How can I tell Typescript that if I use the shorthand ('sauce'
), R[keyof R]
must produce a value of type V
?
CodePudding user response:
Assuming you won't need to type the function body (which is trickier), you can create two overloads to achieve this:
function countBy<R, V>(objs: R[], iteratee: ((x: R) => V)): Map<V, number>
function countBy<R, K extends keyof R, V extends R[K]>(objs: R[], iteratee: K): Map<V, number>
function countBy<R, V>(objs: R[], iteratee: ((x: R) => V) | keyof R): Map<V, number> {
return {} as any
}
CodePudding user response:
To expand upon Oblosys' answer, here is another set of overloads that would achieve your goal:
function countBy<R, K extends keyof R>(
objs: R[],
iteratee: K
): Map<R[K], number>;
function countBy<R, V>(
objs: R[],
iteratee: (x: R) => V
): Map<V, number>;
function countBy<R>(
objs: R[],
iteratee: ((x: R) => unknown) | keyof R
): Map<unknown, number> {
/* ... your code ... */
}
Alternatively, if you don't want to bother with overloads or you want to have a typed body, you can try the following signature, which takes advantage of the fact that you can put conditional types in the return type:
function countBy<R, I extends ((x: R) => any) | keyof R>(
objs: R[],
iteratee: I
): Map<
I extends keyof R ? R[I] : I extends (x: R) => any ? ReturnType<I> : never,
number
> {
/* ... your code ... */
}