Issue
I need some help on my TypeScript adventure.
Here's my example type:
type Target = {
names: string[]
addresses: {
location: string
}[]
};
Here's my example object:
const myTarget: Target = {
names: ["Alex", "Nico"],
addresses: [
{
location: "Miami",
},
{
location: "New York",
},
],
};
And here's my function example:
const changeValue = (
key: keyof Target,
value: string | Record<string, unknown>
) => {
myTarget[key] = [...myTarget[key], value]
}
changeValue('names', 'Elvis')
changeValue('addresses', { location: "Atlanta" })
I've an error on the myTarget[key]
saying this:
Cannot assign type '(string | Record<string, unknown>)[]' to type 'string[] & { location: string; }[]'.
Cannot assign type '(string | Record<string, unknown>)[]' to type 'string[]'.
Cannot assign type 'string | Record<string, unknown>' of type 'string'.
Cannot assign type 'Record<string, unknown>' to type 'string' .ts(2322)
From what I understand is that, for example, TypeScript is testing all combinations, including adding an object to the names
array, which is invalid, or adding a string inside addresses
array.
Can someone please help me to understand what's the problem and how can I handle it. ( the real project is so much more complicated, but maybe with this example I'll understand a bit..)
Thanks a lot.
Solution
In general, where you want to pass a key of an object type and a value of the type of that key, you can do so like this:
const changeValue = <K extends keyof Target, V extends Target[K]>(
key: K,
value: V
) => {
myTarget[key] = value;
}
That is, you tell the compiler that value
is not only the same type of some value of the object, but the same type as the one given by key
.
However, you upped the difficult by playing with arrays. There is not, so far as I know, a utility type to extract the type of an array, so I wrote one:
type ArrayType<T> = T extends Array<infer U>? U : never;
Now we can use it:
const appendValue = <K extends keyof Target, V extends ArrayType<Target[K]>>(
key: K,
value: V
) => {
myTarget[key] = [...myTarget[key], value] as Target[K];
}
Unfortunately, as you see, the compiler gets a little confused about the relationship between V[]
and Target[K]
, so it needs the hint in the form of as
.
Now the calls compile cleanly:
appendValue('names', 'Elvis')
appendValue('addresses', { location: "Atlanta" })
Incidentally, you do some things in your example code (though perhaps not in your actual code) that I would urge you not to do.
First, you have mutable global data, myTarget
. Hic fons lacrimis, do not do this, in any language, if you can at all avoid it.
Also, mutating data in general is a bad idea. Consider this instead:
const appendValue = <K extends keyof Target, V extends ArrayType<Target[K]>>(
target: Target,
key: K,
value: V
): Target => ({
...target,
[key]: [...myTarget[key], value] as Target[K]
})
Answered By - Michael Lorton
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.