Issue
- The following function admit a generic called
T
. - The property
key
only admit a string that should be a key ofT
. We will useObj
asT
. - The property
value
should admit the type ofObj[key]
. Cant manage to implement it.
const fn = <T>({
key,
value
}: {
key: keyof T;
// Value type should be the type of the value of the key 'key' of 'T' generic
value: typeof T[key];
}) => {
return undefined;
};
type Obj = {
keyA: {
propertyFromA: string;
};
keyB: {
propertyFromB: string;
};
};
fn<Obj>({
key: "keyA",
value: { propertyFromB: "string" } // should only admit type { propertyFromA: "string" }, since Im passing 'keyA'
});
Solution
(Aside: I am renaming your types to conform with common TypeScript naming conventions: your Obj
type will become T
, to make it obvious that this is a generic type parameter and not a specific type; and, your obj
type will become Obj
, to make it obvious that this is a TypeScript type and not a JavaScript value. I suggest you make the same changes to the code in your question to make it easier for others.)
The type of value
is something like T[typeof key]
. But since you have typeof key
as being just keyof T
, then this is T[keyof T]
and therefore a union of all possible property value types from T
. Since you want the compiler to only allow value
properties that correspond to the particular subtype of keyof T
the caller uses for key
, this indicates that you need the type of key
to be an additional generic type parameter constrained to keyof T
.
That gives us:
const fn = <T extends object, K extends keyof T>({
key,
value
}: {
key: K;
value: T[K];
}) => {
return undefined;
};
But unfortunately now you can't call
fn<Obj>({ // error!
key: "keyB",
value: { propertyFromB: "string" }
});
That's because TypeScript does not have partial type argument inference as requested in microsoft/TypeScript#26242. See this question and its answer for more detailed information.
When you call a generic function, you either need to manually specify all the type parameters, or not specify any of them and let the compiler infer them. You can't specify one and let the compiler infer the other. Which is sad, because that's exactly the behavior you want. For now there are only workarounds.
One workaround for this is to use currying to split the generic function into two, so callers can specify the type argument to the first function, which then returns another generic function where callers let the compiler infer the remaining type parameters. It looks like this:
const fn = <T extends object>() => <K extends keyof T>({
key,
value
}: {
key: K;
value: T[K];
}) => {
return undefined;
};
So now instead of calling fn<Obj>({...})
, you need to call fn<Obj>()({...})
(note the extra function call). If you're going to use fn<Obj>()
a lot, you can save it to an intermediate function:
const fnObj = fn<Obj>();
And then use it to get the inference you want:
fnObj({
key: "keyA",
value: { propertyFromB: "string" } // error
});
fnObj({
key: "keyB",
value: { propertyFromB: "string" } // okay
});
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.