Reputation: 1161
Alright so here's the problem. I have the following type and function definitions:
export type compareFunction<T> = (t1: T, t2: T) => boolean
function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
return <
T,
K extends keyof T
>(propName: K) => {
return (t1: T, t2: T) => customCompare(t1[propName], t2[propName]) as compareFunction<P>
}
}
My question is, how would place a particular constraint such that T[propName]
is of type P
?
I've tried the following:
function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
return <
T extends { [keys in keyof T]: P },
K extends keyof T
>(propName: K) => {
return (t1: T, t2: T) => customCompare(t1[propName], t2[propName]) as compareFunction<P>
}
}
But this forces ALL properties in T
to map to type P
.
Upvotes: 1
Views: 301
Reputation: 20142
The problem is lack of relation between P
and T
. We can solve that but setting the relation. Consider following code:
export type compareFunction<T> = (t1: T, t2: T) => boolean
function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
return <
T extends Record<K, P>, // pay attention here
K extends keyof T
>(propName: K) => {
return (t1: T, t2: T) => customCompare(t1[propName], t2[propName])
}
}
T extends Record<K, P>
is saying that our type T
is an object which all properties are type of P
. Thanks to that we can do t1[propName]
and we know that its type of P
.
We can achieve that by some additional typing consider:
// utility type which gives us only keys which values in T1 are T2
type OnlyKeysOfT<T1, T2> = {
[K in keyof T1]: T1[K] extends T2 ? K : never
}[keyof T1]
function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
return <
T extends Record<K, P>,
K extends OnlyKeysOfT<T, P> = OnlyKeysOfT<T, P>,
>(propName: K) => {
return (t1: T, t2: T) => customCompare(t1[propName], t2[propName])
}
}
// below only a is correct
createCompareFunctionCreator<string>((a,b) => true)<{a: string, b: number}>('a')
Upvotes: 4